napkin-0.5.14
Safe HaskellSafe-Inferred
LanguageGHC2021

Napkin.Spec.Yaml.Encoding

Synopsis

Documentation

newtype Encoding t a #

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)
 

Constructors

Encoding a 

Instances

Instances details
(Generic a, GFromJSON Zero (Rep a), AesonOptions o a) => FromJSON (Encoding o a) # 
Instance details

Defined in Napkin.Spec.Yaml.Encoding

(Generic a, GToJSON Zero (Rep a), GToEncoding Zero (Rep a), AesonOptions o a) => ToJSON (Encoding o a) # 
Instance details

Defined in Napkin.Spec.Yaml.Encoding

data FieldPrefix (prefix :: Symbol) #

Remove prefix from field name

data CtorPrefix (prefix :: Symbol) #

Remove prefix from constructor names

data CtorTag (name :: Symbol) #

Override tag key for tagged objects

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

data Tagless #

Override tag presence for sum types objects