Safe Haskell | Safe-Inferred |
---|---|
Language | GHC2021 |
Synopsis
- newtype Encoding t a = Encoding a
- data FieldPrefix (prefix :: Symbol)
- data CtorPrefix (prefix :: Symbol)
- data CtorTag (name :: Symbol)
- data SnakeCaseFields
- data SnakeCaseCtors
- data DefaultCtorPrefix
- data DefaultFieldPrefix
- data Tagless
Documentation
This newtype allows to derive ToJSON and FromJSON instances according to our needs with as little boilerplate as possible.
This library provides a few combinators that simplify typical use cases: * Removal of field/constructor name prefix * Conversionfield/constructor names to snake_case * Modifying sum-type tag key
Options are provided as a type-level list.
data Foo = Foo_GreenBlue { _foo_myField :: Int } | Foo_BlueRed { _foo_myOtherField :: Int } deriving (Generic) deriving (ToJSON, FromJSON) via (Encoding '[FieldPrefix "_foo_", CtorPrefix Foo_, SnakeCaseCtors, SnakeCaseFields, CtorTag "name"] Foo)
will serialize to {"name: "green_blue", "my_field": 123}
Rationale:
Most of YAML Spec types have serialization instances implemented manually. While this is required for custom serialization, often one would need generic serialization that would map to JSON/YAML in a straightforward manner. Let's imagine data type
data FooConfig = FooConfig { _fooConfig_user :: Text , _fooConfig_password :: Text , _fooConfig_host :: Text , _fooConfig_port :: Int }
that would likely just serialize to {"user": "...", "password": "...", "host": "...", port: 123}
This could be implemented manually, however manually implemented instances require more testing and it is easier to forget to update them (e.g. add storing Maybe field in toJSON instance), therefore it's easy to break decode . encode = id property.
A lot of ToJSON/FromJSON instances used for YAML encoding could be derived automatically by just
data Foo = ... deriving (Generic, ToJSON, FromJSON) -- requires -XDeviveAnyClass
with no need to implement them manually (and have test suites for them). Less is more.
That however does not allow remove field prefixes (often used by convention, including ours). To solve this one may use genericToJSON, genericToEncoding (optional), genericParseJSON functions that allow passing some options to configure encoding (see Data.Aeson.Options).
data Foo = ... deriving (Generic) instance ToJSON Foo where toJSON = genericToJSON (defaultOptions { fieldLabelModifier = snakeCase . drop 1 }) toEncoding = genericToEncoding (defaultOptions { fieldLabelModifier = snakeCase . drop 1 }) instance ParseJSON Foo where parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = snakeCase . drop 1 })
This, however, leads to a lot of boilerplate to be repeated. Also, there may be inconsistencies between options passed to toJSON, toEncoding and parseJSON that may be hard to debug (e.g. toJSON and toEncoding having different flags – been there done that).
As a convenient workaround one may use the DerivingVia extension that arrived in GHC 8.6. DerivingVia allows to reuse instances from other coercible types (usually a newtype), with a pretty compact syntax:
data Foo = ... deriving (Generic) deriving (ToJSON, FromJSON) via (MyEncoding Foo)
Encoding a |
data FieldPrefix (prefix :: Symbol) #
Remove prefix from field name
data CtorPrefix (prefix :: Symbol) #
Remove prefix from constructor names
data SnakeCaseFields #
snake_case field names (apply after removing prefix)
data SnakeCaseCtors #
snake_case constructor names (apply after removing prefix)
data DefaultCtorPrefix #
Remove prefix TypeName_
from constructor names
data DefaultFieldPrefix #
Remove prefix _typeName_
from field names