| Copyright | (c) 2019 Felix Paulusma |
|---|---|
| License | MIT |
| Maintainer | felix.paulusma@gmail.com |
| Stability | experimental |
| Safe Haskell | None |
| Language | Haskell2010 |
Data.SafeJSON
Contents
Description
Please read the
for an extensive explanation of this library, why and how to use it, and examples.
- safeToJSON :: forall a. SafeJSON a => a -> Value
- safeFromJSON :: forall a. SafeJSON a => Value -> Parser a
- class (ToJSON a, FromJSON a) => SafeJSON a where
- data Contained a
- contain :: a -> Contained a
- data Version a
- noVersion :: Version a
- data Kind a
- base :: Kind a
- extension :: (SafeJSON a, Migrate a) => Kind a
- extended_base :: (SafeJSON a, Migrate (Reverse a)) => Kind a
- extended_extension :: (SafeJSON a, Migrate a, Migrate (Reverse a)) => Kind a
- typeName0 :: Typeable a => Proxy a -> String
- typeName1 :: forall t a. Typeable t => Proxy (t a) -> String
- typeName2 :: forall t a b. Typeable t => Proxy (t a b) -> String
- typeName3 :: forall t a b c. Typeable t => Proxy (t a b c) -> String
- typeName4 :: forall t a b c d. Typeable t => Proxy (t a b c d) -> String
- typeName5 :: forall t a b c d e. Typeable t => Proxy (t a b c d e) -> String
- data Profile a
- data ProfileVersions = ProfileVersions {}
- class SafeJSON (MigrateFrom a) => Migrate a where
- type MigrateFrom a
- newtype Reverse a = Reverse {
- unReverse :: a
Conversion to/from versioned JSON
These functions are the workhorses of the library.
As long as a type has a SafeJSON instance and, if conversion
from other types is required, a Migrate instance, these will
make sure to add and read version numbers, and handle migration.
safeToJSON :: forall a. SafeJSON a => a -> Value Source #
Use this exactly how you would use toJSON from Data.Aeson.
Though most use cases will probably use one of the encode
functions from Data.Aeson.Safe.
safeToJSON will add a version tag to the Value created.
If the Value resulting from safeTo (by default the same as toJSON)
is an Object, an extra field with the version number will be added.
Example value:
{"type":"test", "data":true}
Resulting object:
{"!v": 1, "type":"test", "data":true}If the resulting Value is not an Object, it will be wrapped
in one, with a version field:
Example value:
"arbitrary string"
Resulting object:
{"~v": 1, "~d": "arbitrary string"}This function does not check consistency of the SafeJSON instances.
It is advised to always testConsistency for all
your instances in a production setting.
safeFromJSON :: forall a. SafeJSON a => Value -> Parser a Source #
Use this exactly how you would use parseJSON from Data.Aeson.
Though most use cases will probably use one of the decode
functions from Data.Aeson.Safe.
safeFromJSON tries to find the version number in the JSON
Value provided, find the appropriate parser and migrate the
parsed result back to the requested type using Migrate
instances.
If there is no version number (that means this can also happen with
completely unrelated JSON messages), and there is a SafeJSON
instance in the chain that has version defined as noVersion,
it will try to parse that type.
N.B. If the consistency of the SafeJSON instance in
question is faulty, this will always fail.
SafeJSON Class
This class, together with Migrate, is where the magic happens!
Using the SafeJSON class to define the form and expected
migration to a type, and defining Migrate instances to describe
how to handle the conversion from older versions (or maybe a
newer version) to the type, you can be sure that your programs
will still parse the JSON of types it is expecting.
class (ToJSON a, FromJSON a) => SafeJSON a where Source #
A type that can be converted from and to JSON with versioning baked
in, using Migrate to automate migration between versions, reducing
headaches when the need arrises to modify JSON formats while old
formats can't simply be disregarded.
Methods
The version of the type.
Only used as a key so it must be unique (this is checked at run-time)
Version numbering doesn't have to be sequential or continuous.
The default version is 0 (zero).
The kind specifies how versions are dealt with. By default, values are tagged with version 0 and don't have any previous versions.
The default kind is base
safeTo :: a -> Contained Value Source #
This method defines how a value should be serialized without worrying
about adding the version. The default implementation uses toJSON, but
can be modified if need be.
This function cannot be used directly. Use safeToJSON, instead.
safeFrom :: Value -> Contained (Parser a) Source #
This method defines how a value should be parsed without also worrying
about writing out the version tag. The default implementation uses parseJSON,
but can be modified if need be.
This function cannot be used directly. Use safeFromJSON, instead.
typeName :: Proxy a -> String Source #
The name of the type. This is used in error message strings and the
Profile report.
Doesn't have to be defined if your type is Typeable. The default
implementation is typeName0. (cf. typeName1, typeName2, etc.)
typeName :: Typeable a => Proxy a -> String Source #
The name of the type. This is used in error message strings and the
Profile report.
Doesn't have to be defined if your type is Typeable. The default
implementation is typeName0. (cf. typeName1, typeName2, etc.)
objectProfile :: Profile a Source #
Version profile.
Shows the current version of the type and all supported versions it can migrate from.
Contained
This is an inpenetrable container. A security measure
used to ensure safeFrom and safeTo are never used
directly. Instead, always use safeFromJSON and
safeToJSON.
Version
All SafeJSON instances have a version. This version will be
attached to the JSON format and used to figure out which parser
(and as such, which type in the chain) should be used to parse
the given JSON.
A simple numeric version id.
Version has a Num instance and should be
declared using integer literals: version = 2
Instances
| Eq (Version a) Source # | |
| Num (Version a) Source # | It is strongly discouraged to use any methods other
than |
| Show (Version a) Source # | |
| Arbitrary (Version a) Source # | This instance explicitly doesn't consider |
noVersion :: Version a Source #
This is used for types that don't have a version tag.
This is used for primitive values that are tagged with
a version number, like Int, Text, [a], etc.
But also when implementing SafeJSON after the fact,
when a format is already in use, but you still want to
be able to migrate from it to a newer type or format.
N.B. version = noVersion is distinctively different
from version = 0, which will add a version tag with
the number 0 (zero), whereas noVersion will not add a
version tag.
Kind
All SafeJSON instance have a declared kind, indicating if any
migration needs to happen when parsing using safeFromJSON.
- The Base kind (see
base) is at the bottom of the chain and will not be migrated to. They can optionally have no version tag by defining:. N.B.version=noVersionbaseandextended_baseare the only kinds that can be paired withnoVersion. - Extensions (see
extensionandextended_extension) tell the system that there exists at least one previous version of the data type which should be migrated from if needed. (This requires the data type to also have aMigrate ainstance) - Forward extensions (see
extended_baseandextended_extension) tell the system there exists at least one next version from which the data type can be reverse-migrated. (This requires the data type to also have aMigrate (Reverse a)instance)
extension :: (SafeJSON a, Migrate a) => Kind a Source #
Used to define kind.
Extends a previous version.
extended_base :: (SafeJSON a, Migrate (Reverse a)) => Kind a Source #
Used to define kind.
Types that are extended_base, are extended by a
future version and as such can migrate backward from
that future version. (cf. extended_extension, base)
extended_extension :: (SafeJSON a, Migrate a, Migrate (Reverse a)) => Kind a Source #
Used to define kind.
Types that are extended_extension are extended
by a future version and as such can migrate from
that future version, but they also extend a previous
version. (cf. extended_base, extension)
Showing the type
These helper functions can be used to easily define typeName.
As long as the type being defined has a Typeable instance.
typeName0 :: Typeable a => Proxy a -> String Source #
Type name string representation of a nullary type constructor.
typeName1 :: forall t a. Typeable t => Proxy (t a) -> String Source #
Type name string representation of a unary type constructor.
typeName2 :: forall t a b. Typeable t => Proxy (t a b) -> String Source #
Type name string representation of a binary type constructor.
typeName3 :: forall t a b c. Typeable t => Proxy (t a b c) -> String Source #
Type name string representation of a ternary type constructor.
typeName4 :: forall t a b c d. Typeable t => Proxy (t a b c d) -> String Source #
Type name string representation of a 4-ary type constructor.
typeName5 :: forall t a b c d e. Typeable t => Proxy (t a b c d e) -> String Source #
Type name string representation of a 5-ary type constructor.
Consistency
Profile of the internal consistency of a SafeJSON instance.
N.B. noVersion shows as null instead of a number.
Constructors
| InvalidProfile String | There is something wrong with versioning |
| Profile ProfileVersions | Profile of consistent versions |
data ProfileVersions Source #
Version profile of a consistent SafeJSON instance.
Constructors
| ProfileVersions | |
Fields
| |
Instances
| Eq ProfileVersions Source # | |
| Show ProfileVersions Source # |
|
Migration
class SafeJSON (MigrateFrom a) => Migrate a where Source #
This instance is needed to handle the migration between older and newer versions.
Note that, where (Migrate a) migrates from the previous
version to the type a, (Migrate (Reverse a)) migrates
from the future version to the type a.
Example
Two types that can migrate to each other.
(Don't forget to give OldType one of the extended kinds,
and NewType one of the extension kinds.)
instanceMigrateNewType where typeMigrateFromNewType = OldTypemigrateOldType = NewType instanceMigrate(Reverse OldType) where typeMigrateFrom(Reverse OldType) = NewTypemigrateNewType = Reverse OldType
Minimal complete definition
Methods
migrate :: MigrateFrom a -> a Source #
The migration from the previous version to the
current type a. OR, in case of a (Reverse a),
the migration from the future version back to
the current type a