aeson-diff-generic: Apply a json-patch to any haskell datatype.

[ bsd3, json, library, web ] [ Propose Tags ]

Apply a json-patch directly to a haskell datatype. It extends the capabilities of the aeson-diff packages, and includes template haskell functions for automatically deriving the right instances.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.0.1, 0.0.2, 0.0.3
Change log changelog.md
Dependencies aeson (>=1.2.4.0 && <1.3), aeson-diff (>=1.1.0.0 && <1.2), base (>=4.5 && <5), base-compat (>=0.9.1 && <0.10), bytestring (>=0.10 && <0.11), containers (>=0.5.8), dlist (>=0.6), fail (>=4.9 && <4.10), hashable (>=1.1.2.0), lens (>=4.16 && <4.17), nats (>=1 && <1.2), scientific (>=0.3.4.7 && <0.4), semigroups (>=0.18.2 && <0.19), tagged (>=0.8.3 && <0.9), template-haskell (>=2.7), text (>=1.1.1.0), th-abstraction (>=0.2.2 && <0.3), time (>=1.1.1.4), transformers (>=0.2.2.0), transformers-compat (>=0.3), unordered-containers (>=0.2.5.0 && <0.3), uuid-types (>=1.0.3 && <1.1), vector (>=0.8) [details]
License BSD-3-Clause
Copyright Kristof Bastiaensen (2018)
Author Kristof Bastiaensen
Maintainer Kristof Bastiaensen
Category JSON, Web
Bug tracker https://github.com/kuribas/aeson-diff-generic/issues
Source repo head: git clone https://github.com/kuribas/aeson-diff-generic
Uploaded by KristofBastiaensen at 2018-04-17T10:45:38Z
Distributions
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 1894 total (8 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2018-04-17 [all 1 reports]

Readme for aeson-diff-generic-0.0.3

[back to package description]

Welcom to aeson-diff-generic Hackage Build Status

aeson-diff-generic is a haskell library that allows you to apply a JSON patch rfc6902 document directly to a haskell datatype. A JSON Patch document is a sequence of instructions to modify a JSON value. It is suitable for use with the HTTP PATCH method.

Suppose you are writing a client/server application with auto-save. Every time the user makes a change to the document, the client needs to tell the server about it. If the document being edited is large, sending the entire updated document is impractical, so you want to send a diff instead. JSON patch rfc6902 is a standard format for representing diffs, but it applies to JSON documents, not Haskell values. Let's say our document is represented by a Haskell value of type Doc: we need a Doc patch, not a json patch.

The aeson library uses GHC.Generics or template haskell to define a default json encoding for algebraic datatypes. The aeson-diff-generic can generate code, using the same options as given to aeson, to interpret a json patch as a Doc patch.

Example

Suppose we have a datatype for which we have a ToJSON and FromJSON instance:

{-# LANGUAGE DeriveGeneric, TemplateHaskell, OverloadedStrings #-}
import Data.Aeson
import Data.Aeson.Diff
import Data.Aeson.Diff.Generic
import GHC.Generics

data Pet = Bird | Cat | Dog | Fish
  deriving (Show, Eq, Generic)

data Person = Person
  { name :: String
  , age  :: Int
  , pet  :: Pet
  } deriving (Show, Eq, Generic)

instance ToJSON Pet where
  toJSON = genericToJSON defaultOptions
  toEncoding = genericToEncoding defaultOptions
  
instance FromJSON Pet where
  parseJSON = genericParseJSON defaultOptions

instance ToJSON Person where
  toJSON = genericToJSON defaultOptions
  toEncoding = genericToEncoding defaultOptions
  
instance FromJSON Person where
  parseJSON = genericParseJSON defaultOptions

We can now create a JsonPatch instance for our datatypes. Creating one by hand is tedious, so aeson-diff-generic gives two alternative ways to create one: using the FieldLens class, or using template haskell with the "Data.Aeson.Diff.Generic.TH" module.

Creating instances with fieldlens.

A fieldlens maps a Key onto a getter and setter into the given data.

For our Pet datatype we don't have any fields to index into, so it just returns an error:

instance FieldLens Pet where
  fieldLens _ _ = Error "Invalid Path"

Since this is the default implementation, we can do simply:

instance FieldLens Pet

For the Person datatype we map each field to the GetSet type:

instance FieldLens Person where
  fieldLens (OKey field) (Person name_ age_ pet_) =
    case field of
      "name" -> pure $ GetSet name_ (\v -> pure $ Person v age_ pet_)
      "age"  -> pure $ GetSet age_ (\v -> pure $ Person name_ v pet_)
      "pet"  -> pure $ GetSet pet_ (\v -> pure $ Person name_ age_ v)
      _ -> Error "Invalid Path"
  fieldLens _ _ = Error "Invalid Path"

Now our JsonPatch instance will automatically use the FieldLens instance.

instance JsonPatch Person
instance JsonPatch Pet

Creating instances with template haskell

FieldLens still involves some boilerplate. We can avoid that by using the template haskell functions from "Data.Aeson.Diff.Generic.TH":

instance JsonPatch Pet
instance FieldLens Pet

deriveJsonPatch defaultOptions ''Person

applying patches

Now we can apply patches to our data:

> joe = Person "Joe" 32 Fish
> john = Person "John" 32 Bird
> patch = Data.Aeson.Diff.diff (toJSON joe) (toJSON john)

> import qualified Data.ByteString.Lazy.Char8 as ByteString
> ByteString.putStrLn $ encode patch
[{"op":"replace","path":"/name","value":"John"},{"op":"replace","path":"/pet","value":"Bird"}]

> Success json = Data.Aeson.Diff.patch patch (toJSON joe)
> ByteString.putStrLn $ encode json
{"age":32,"name":"John","pet":"Bird"}

> Data.Aeson.Diff.Generic.patch patch joe
Success (Person {name = "John", age = 32, pet = Bird})

Join in!

I am happy to receive bug reports, fixes, documentation enhancements, and other improvements.

Please report bugs via the github issue tracker.

Master git repository:

  • git clone git://github.com/kuribas/aeson-dif-generic.git

See what's changed in recent (and upcoming) releases:

(You can create and contribute changes using git)

Authors

This library is written by Kristof Bastiaensen.