schemas: schema guided serialization

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Schemas is a Haskell library for serializing and deserializing data in JSON. With schemas one does not define parsing and encoding functions, instead one defines a schema that explains the "shape" of the data, and the library provides the encode and decode functions. Shape descriptions are statically typed.

Schemas are related by a subtyping relation, which can be used to implement a simple form of schema versioning. As long as one knows the source and target schemas, and the source is a subtype of the target, source values can be encoded in the target schema.

The library also supports oneOf and allOf schemas, which extend the range of versioning changes that can be supported automatically without resorting to explicit versions and conversion functions.

A type class HasSchema is provided purely for convenience, but none of the core functions in the library rely on type classes.

Schemas can be derived generically using generics-sop, although most of the time it makes more sense to define the schemas explicitly to ensure well-behaved versioning.


[Skip to Readme]

Properties

Versions 0.1.0.0, 0.1.1.0, 0.1.1.0, 0.2.0, 0.2.0.1, 0.2.0.2, 0.2.0.3, 0.3.0, 0.3.0.1, 0.3.0.2, 0.4.0.0, 0.4.0.1, 0.4.0.2
Change log CHANGELOG.md
Dependencies aeson, aeson-pretty, base (>=4.12.0.0 && <4.13), bifunctors, bytestring, free, generic-lens, generics-sop (>=0.5.0.0), hashable, lens, lens-aeson, pretty-simple, profunctors, scientific, text, transformers, unordered-containers, vector [details]
License BSD-3-Clause
Author Pepe Iborra
Maintainer pepeiborra@gmail.com
Category Data
Home page https://github.com/pepeiborra/schemas
Bug tracker https://gitlab.com/pepeiborra/schemas/issues
Source repo head: git clone https://github.com/pepeiborra/schemas.git
Uploaded by PepeIborra at 2019-09-28T16:50:48Z

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for schemas-0.1.1.0

[back to package description]

CI Hackage

schemas

A library for schema-guided serialization of Haskell data types.

Features

Why schemas

schemas is a Haskell-centric serialization library written with versioning in mind. Since a schema is a first-class citizen, it can be serialized and reasoned about. Serialization and deserialization require a source and target schema, and versioning is accomplished by checking that the two schemas are related by a subtyping relation. There is no need to keep old versions of datatypes around nor to write code for upgrades/downgrades.

Consider a schema modification that adds a field. To support upgrading old documents to the new schema, the only requirement is that the new field is optional. Downgrading is easy too, simply omit the new field. Conversely, a schema modifcation that removes a field supports trivial upgrades but the removed field must be optional to support downgrading. Changing the type of a field is supported in as much as the old and new types are relatable. Field renaming is not supported. More importantly, all these changes are defined by a schema relation, and the library provides a predicate to check whether the relation holds.

schemas can also be used in a GraphQL-like fashion, allowing clients to request a subset of the schema. This comes up specially when working with recursive schemas involving cyclic data.

Subtyping relation

schemas relies on a simple subtyping relation between schemas to perform value conversions. The basic idea is that these conversions are fully guided by the source and target schemas and involve only simple projections and injections:

  1. Projecting a subset of the source record fields.
  2. Turning a source field of type A into a target field of type Array A.

For more concrete details on the subtyping relation check the definition of isSubtypeOf. This function returns a witness, i.e. a conversion function, whenever the relation holds.

Versioning makes use of this subtyping relation as follows. Downgrading a value v_2 :: T_2 into a previous version T_1 is accomplished via the witness of schema(T_1) < schema(T_2). Similarly, upgrading a v_1 :: T_1 message into a newer version T_2 can be accomplished via the witness of schema(T_1) > schema(T_2). Therefore, a type T_1 can only be replaced by a type T_2 in an downgrade-compatible way if schema(T_1) < schema(T_2); if upggrades are required, then schema(T_1) > schema(T_2) is required too.

The < relation is reflexive and transitive, but importantly not asymmetric or antisymmetric: it can be that both T_1 < T_2 and T_2 < T_1 and yet they are not the same type. For example, given a S_2 schema that adds a required field to S_1, we would have that S_1 < S_2 but not S_1 > S_2. However, if new the field was optional, then we would have S_1 > S_2 too. In such case, we say that S_1 ~ S_2 because they only differ on optional fields. For example, given a S_3 schema that removes a field from S_2, we have:

The ~ relation is an equivalence class, i.e. it is reflexive, symmetric and transitive.

Alternative encodings

Sometimes there is more than one way to encode a value. A field can be renamed or change its type, an optional field become mandatory, several fields can be merged into one, etc. Alternative encodings allow for backwards compatible schema evolution.

Schemas support alternative encodings via the 'Or' constructor. The schema A|B encodes a value in two alternative ways A and B. A message created with this schema may encodings A, B or both. 'encode' will always create messages with all the possible encodings. While messages with multiple alternative encodings are not desirable for serialization, the desired message can be carved out using the subtyping relation. All the following hold:

A|B < A (the coercion A -> A|B will produce a message with an A encoding)
A|B < B (the coercion B -> A|B will produce a message with a  B encoding)
A < A|B (the coercion A|B -> A will succeed only if the message contains an A encoding)
B < A|B (the coercoin A|B -> B will succeed only if the message contains a  B encoding)

Typed schemas implement a limited form of alternative encodings via the Alternative instance for record fields. In the future a similar 'Alternative' instance for union constructors could be added.

Example