jijo: Bidirectional JSON serialization
Explicitness – decouple types and encoders/decoders (unlike autoderived instances in Aeson).
Bidirectionality – use the same definition for encoding and decoding to prevent mistakes when one side of the definition is updated and the other is not.
Completeness – collect as many validation errors as possible, instead of stopping after the first error.
JSON.Definition– the core of the framework, includes combinators for defining complete JSON definitions, parsing primitives, objects, sums, and adding predicates to validate complex conditions.
JSON.Validation– the validation machinery, complex enough to deserve its own module.
JSON.Path– utilities for working with JSONPath, which is used for error reporting.
RecordField.*– helpers for generating record constructors that make it harder to mix up fields when decoding records from JSON.
$ stack repl
> uuid <- Data.UUID.V4.nextRandom > encodeViaDefinition jUUID uuid String "c7d63bec-517b-48d8-b77a-bc44d05f24af"
Decoding, happy path:
> import Data.Aeson > validateViaDefinition jUUID (String "c7d63bec-517b-48d8-b77a-bc44d05f24af") Right c7d63bec-517b-48d8-b77a-bc44d05f24af
Decoding, type mismatch:
> validateViaDefinition jUUID (Number 42) Left (JValidationReport [JTypeNotOneOf (fromList [JTyString])] (fromList ))
Decoding, malformed UUID:
> validateViaDefinition jUUID (String "invalid") Left (JValidationReport [JValidationFail InvalidUUID] (fromList ))
The errors are returned as a prefix tree of
JValidationErrors indexed by
JPathSegment. They can include domain-specific errors.
JValidation is defined as follows:
data JValidation e a = JValidation (Maybe a) (JValidationReport e) data JValidationReport e = JValidationReport [JValidationError e] (Map JPathSegment (JValidationReport e))
eis the type of domain-specific errors.
jis the validation input (for example,
ais the validation result.
Applicative instance for
JValidation accumulates errors from all
subcomputations. We don't want to have a
Monad instance for
because it would violate the
(<*>) = ap law.
JDefinition is a categorical (arrow) product of a validator and an encoder:
type JDefinition e = ArrPair (ValidationArr e) EncodingArr data ArrPair p q j a = ArrPair (p j a) (q j a) newtype ValidationArr e j a = ValidationArr (j -> JValidation e a) newtype EncodingArr j a = EncodingArr (a -> j)
It has a
Category instance that can be used for sequential/monadic
validation: any failed step of the pipeline aborts the pipeline. In most
JDefinition can be built by using the same recipe:
- narrow down the type using one of existing primitive combinators
jObject, etc), parse (probably using
- then add extra predicates using
JObjectDefinition is an applicative
Product of a validator and an encoder.
It can be converted into a
JDefinition. It does not have a
but it can be used for "parallel" applicative validation – all errors will be
reported in parallel.
jField uses explicit type applications so that fields would not be mixed
makeRecBuilder wraps constructors into something that takes explicitly
coerceonce GHC 8.10 is out (due to three-release policy).
Better document how to use sum type validation. There are tests in
JSON.DefinitionSpecbut no docs yet.
TODO: comment on
Use something like Barbies or
higgledyto get rid of
Fieldand move field names into types?