Maintainer | Brandon Chinn <brandon@leapyear.io> |
---|---|
Stability | experimental |
Portability | portable |
Safe Haskell | None |
Language | Haskell2010 |
Template Haskell definitions for doing various aeson-schemas
operations.
SchemaType
defines the shape of the JSON object stored in
Object
, and we can use getKey
to lookup a key that
is checked at compile-time to exist in the object.
To make it easier to extract deeply nested keys, this module defines QuasiQuoters that generate the
corresponding getKey
expressions.
In addition to the QuasiQuotes extension, the following extensions will need to be enabled to use these QuasiQuoters:
- DataKinds
- FlexibleContexts
- TypeFamilies
Synopsis
- schema :: QuasiQuoter
- get :: QuasiQuoter
- unwrap :: QuasiQuoter
- mkGetter :: String -> String -> Name -> String -> DecsQ
- mkEnum :: String -> [String] -> Q [Dec]
- genFromJSONEnum :: Name -> Q [Dec]
- genToJSONEnum :: Name -> Q [Dec]
Documentation
schema :: QuasiQuoter Source #
Defines a QuasiQuoter for writing schemas.
Example:
import Data.Aeson.Schema (schema) type MySchema = [schema| { foo: { a: Int, // you can add comments like this nodes: List { b: Maybe Bool, }, c: Text, d: Text, e: MyType, f: Maybe List { name: Text, }, }, } |]
Syntax:
{ key: <schema>, ... }
corresponds to a JSONObject
with the given key mapping to the given schema.Bool
,Int
,Double
, andText
correspond to the usual Haskell values.Maybe <schema>
andList <schema>
correspond toMaybe
and[]
, containing values specified by the provided schema (no parentheses needed).Try <schema>
corresponds toMaybe
, where the value will beJust
if the given schema successfully parses the value, orNothing
otherwise. Different fromMaybe <schema>
, where parsing{ "foo": true }
with{ foo: Try Int }
returnsNothing
, whereas it would be a parse error with{ foo: Maybe Int }
(added in v1.2.0)- Any other uppercase identifier corresponds to the respective type in scope -- requires a FromJSON instance.
Advanced syntax:
<schema1> | <schema2>
corresponds to a JSON value that matches one of the given schemas. When extracted from anObject
, it deserializes into aJSONSum
object. (added in v1.1.0){ [key]: <schema> }
uses the current object to resolve the keys in the given schema. Only object schemas are allowed here. (added in v1.2.0){ key: #Other, ... }
maps the given key to theOther
schema. TheOther
schema needs to be defined in another module.{ #Other, ... }
extends this schema with theOther
schema. TheOther
schema needs to be defined in another module.
get :: QuasiQuoter Source #
Defines a QuasiQuoter for extracting JSON data.
Example:
let Just result = decode ... :: Maybe (Object MySchema) [get| result.foo.a |] :: Int [get| result.foo.nodes |] :: [Object (..)] [get| result.foo.nodes[] |] :: [Object (..)] [get| result.foo.nodes[].b |] :: [Maybe Bool] [get| result.foo.nodes[].b! |] :: [Bool] -- runtime error if any values are Nothing [get| result.foo.c |] :: Text [get| result.foo.(a,c) |] :: (Int, Text) [get| result.foo.[c,d] |] :: [Text] let nodes = [get| result.foo.nodes |] flip map nodes $ \node -> fromMaybe ([get| node.num |] == 0) [get| node.b |] map [get| .num |] nodes
Syntax:
x.y
is only valid ifx
is anObject
. Returns the value of the keyy
..y
returns a function that takes in anObject
and returns the value of the keyy
.x.[y,z.a]
is only valid ifx
is anObject
, and ify
andz.a
have the same type. Returns the value of the operationsy
andz.a
as a list. MUST be the last operation.x.(y,z.a)
is only valid ifx
is anObject
. Returns the value of the operationsy
andz.a
as a tuple. MUST be the last operation.x!
is only valid ifx
is aMaybe
. Unwraps the value ofx
from aJust
value and errors (at runtime!) ifx
isNothing
.x[]
is only valid ifx
is a list. Applies the remaining rules as anfmap
over the values in the list, e.g.x?
follows the same rules asx[]
except it's only valid ifx
is aMaybe
.x@#
is only valid ifx
is aSumType
. If the sum type contains a value at the given branch (e.g.x@0
forHere v
), returnJust
that value, otherwiseNothing
. (added in v1.1.0)
e.g. with the schema { a: Int | Bool }
, calling [get| .a@0 |]
will return Maybe Int
if
the sum type contains an Int
.
unwrap :: QuasiQuoter Source #
Defines a QuasiQuoter to extract a schema within the given schema.
The base schema needs to be defined in a separate module.
For example:
-- | MyFoo ~ Object [schema| { b: Maybe Bool } |] type MyFoo = [unwrap| MySchema.foo.nodes[] |]
If the schema is imported qualified, you can use parentheses to distinguish it from the expression:
type MyFoo = [unwrap| (MyModule.Schema).foo.nodes[] |]
You can then use the type alias as usual:
parseBar :: MyFoo -> String parseBar = maybe "null" show . [get| .b |] foo = map parseBar [get| result.foo.nodes[] |]
The syntax is mostly the same as get
, except the operations run on the
type itself, instead of the values. Differences from get
:
x!
is only valid ifx
is aMaybe a
type. Returnsa
, the type wrapped in theMaybe
.x?
is the same asx!
.x[]
is only valid ifx
is a[a]
type. Returnsa
, the type contained in the list.x@#
is only valid ifx
is aSumType
. Returns the type at that branch in the sum type.
Utilities
mkGetter :: String -> String -> Name -> String -> DecsQ Source #
A helper that generates a get
expression and a type alias for the result
of the expression.
mkGetter "Node" "getNodes" ''MySchema ".nodes[]" {- is equivalent to -} -- | Node ~ { b: Maybe Bool } type Node = [unwrap| MySchema.nodes[] |] getNodes :: Object MySchema -> [Node] getNodes = [get| .nodes[] |]
mkGetter
takes four arguments:
unwrapName
- The name of the type synonym to store the unwrapped schema as
funcName
- The name of the getter function
startSchema
- The schema to extract/unwrap from
ops
- The operation to pass to the
get
andunwrap
quasiquoters
There is one subtlety that occurs from the use of the same ops
string for both the
unwrap
and get
quasiquoters:
unwrap
strips out intermediate functors, while get
applies within the functor. So in the above example, ".nodes[]"
strips out the list when
saving the schema to Node
, while in the below example, ".nodes"
doesn't strip out the list
when saving the schema to Nodes
.
mkGetter "Nodes" "getNodes" ''MySchema ".nodes" {- is equivalent to -} -- | Nodes ~ List { b: Maybe Bool } type Nodes = [unwrap| MySchema.nodes |] getNodes :: Object MySchema -> Nodes getNodes = [get| .nodes |]
As another example,
mkGetter "MyName" "getMyName" ''MySchema ".f?[].name" {- is equivalent to -} -- | MyName ~ Text type MyName = [unwrap| MySchema.f?[].name |] getMyBool :: Object MySchema -> Maybe [MyName] getMyBool = [get| .f?[].name |]
Helpers for Enum types
mkEnum :: String -> [String] -> Q [Dec] Source #
Make an enum type with the given constructors, that can be parsed from JSON.
The FromJSON
instance will match to a string value matching the constructor name,
case-insensitive.
mkEnum "State" ["OPEN", "CLOSED"] -- generates equivalent of: -- data State = OPEN | CLOSED deriving (...) -- genFromJSONEnum ''State -- genToJSONEnum ''State
genFromJSONEnum :: Name -> Q [Dec] Source #
Generate an instance of FromJSON
for the given data type.
Prefer using mkEnum
; this function is useful for data types in which you want greater control
over the actual data type.
The FromJSON
instance will match to a string value matching the constructor name,
case-insensitive.
data State = Open | CLOSED deriving (Show,Enum) genFromJSONEnum ''State -- outputs: -- Just Open -- Just Open -- Just CLOSED -- Just CLOSED main = mapM_ print [ decodeState "open" , decodeState "OPEN" , decodeState "closed" , decodeState "CLOSED" ] where decodeState :: String -> Maybe State decodeState = decode . show
genToJSONEnum :: Name -> Q [Dec] Source #
Generate an instance of ToJSON
for the given data type.
Prefer using mkEnum
; this function is useful for data types in which you want greater control
over the actual data type.
The ToJSON
instance will encode the enum as a string matching the constructor name.
data State = Open | CLOSED deriving (Show,Enum) genToJSONEnum ''State -- outputs: -- "Open" -- "CLOSED" main = mapM_ print [ encode Open , encode CLOSED ]