{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} -- | JsonGrammar allows you to express a bidirectional mapping between Haskell datatypes and JSON ASTs in one go. module Language.JsonGrammar ( -- * The Aeson example -- $example -- * Types Grammar, Context(..), (:-)(..), -- * Elemental building blocks pure, many, literal, label, object, property, array, element, coerce, -- * Constructing grammars fromPrism, defaultValue, -- * Wrapping constructors nil, cons, tup2, -- * Type-directed grammars Json(..), el, prop, -- * Using grammars parse, serialize, interfaces, SomeGrammar(..) ) where import Prelude (Monad(..), Maybe(..)) import Control.Category ((.)) import Data.Aeson.Types (Parser) import Language.JsonGrammar.Grammar import Language.JsonGrammar.Parser import Language.JsonGrammar.Serializer import Language.JsonGrammar.TypeScript -- $example -- -- Aeson provides this example datatype: -- -- > data Person = Person -- > { name :: Text -- > , age :: Int -- > } deriving Show -- -- With these conversion functions: -- -- > {-# LANGUAGE OverloadedStrings #-} -- > -- > instance FromJSON Person where -- > parseJSON (Object v) = Person <$> -- > v .: "name" <*> -- > v .: "age" -- > -- A non-Object value is of the wrong type, so fail. -- > parseJSON _ = mzero -- > -- > instance ToJSON Person where -- > toJSON (Person name age) = object ["name" .= name, "age" .= age] -- -- From JsonGrammar's point of view, the problem with writing the conversions this way is that the same thing is written down twice: from one conversion, one can figure out what the conversion in the opposite direction should look like. -- -- In JsonGrammar, the conversion looks like this: -- -- > {-# LANGUAGE TemplateHaskell #-} -- > -- > deriveStackPrismsFor ["person"] ''Person -- > -- > instance Json Person where -- > grammar = fromPrism person . object (prop "name" . prop "age") -- -- This expresses the conversion in both directions in one go. The resulting parser and serializer are each other's inverse by construction. -- -- As a bonus, if you name your grammar, JsonGrammar will generate a TypeScript definition for you: -- -- > instance Json Person where -- > grammar = label "Person" $ -- > fromPrism person . object (prop "name" . prop "age") -- -- This results in this TypeScript definition: -- -- > interface Person {age : number ;name : string ;} -- | Parse a JSON value according to the specified grammar. parse :: Grammar 'Val (a :- ()) (b :- ()) -> a -> Parser b parse = parseValue . unstack -- | Serialize a Haskell value to a JSON value according to the specified grammar. serialize :: Grammar 'Val (a :- ()) (b :- ()) -> b -> Maybe a serialize = serializeValue . unstack unstack :: Grammar c (a :- ()) (b :- ()) -> Grammar c a b unstack g = pure hd unhd . g . pure unhd hd where hd (x :- ()) = return x unhd x = return (x :- ())