generic-aeson-0.2.0.3: src/Generics/Generic/Aeson.hs
{-# LANGUAGE
CPP
, FlexibleContexts
, FlexibleInstances
, OverlappingInstances
, OverloadedStrings
, ScopedTypeVariables
, TupleSections
, TypeOperators
#-}
-- | This module offers generic conversions to an from JSON 'Value's
-- for data types with a 'Generic' instance.
--
-- The structure of the generated JSON is meant to be close to
-- idiomatic JSON. This means:
--
-- * Enumerations are converted to JSON strings.
--
-- * Record fields become JSON keys.
--
-- * Data types with one unlabeled field convert to just that field.
--
-- * Data types with multiple unlabeled fields become arrays.
--
-- * Multiple constructors are represented by keys.
--
-- * 'Maybe' values are either an absent key, or the value.
--
-- See 'tests/Main.hs' for more examples.
module Generics.Generic.Aeson
( gtoJson
, gparseJson
, GtoJson (..)
, GfromJson (..)
, formatLabel
, Settings (..)
, defaultSettings
, gtoJsonWithSettings
, gparseJsonWithSettings
) where
import Control.Applicative
import Control.Monad.State
import Data.Aeson
import Data.Aeson.Types hiding (GFromJSON, GToJSON)
import Data.Proxy
import Data.Text (Text)
import GHC.Generics
import Generics.Deriving.ConNames
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import qualified Data.Vector as V
import Generics.Generic.Aeson.Util
-- | Class for converting the functors from "GHC.Generics" to JSON.
-- You generally don't need to give any custom instances. Just add
-- 'deriving Generic' and call 'gToJson'.
class GtoJson f where
-- | Generically show a functor as a JSON value. The first argument
-- tells us if there are multiple constructors in the data type. The
-- second indicates if this data type is an enumeration (only empty
-- constructors). A functor is then converted to either a list
-- of values (for non-labeled fields) or a list of String/value
-- pairs (for labeled fields).
gtoJSONf :: Settings -> Bool -> Bool -> f a -> Either [Value] [(Text, Value)]
-- | Class for parsing the functors from "GHC.Generics" from JSON.
-- You generally don't need to give any custom instances. Just add
-- 'deriving Generic' and call 'gFromJson'.
class GfromJson f where
-- | Generically read a functor from a JSON value. The first
-- argument tells us if there are multiple constructors in the data
-- type. The second indicates if we've already detected that this
-- data type has multiple constructors. When this is False, the
-- (:*:) puts the fields in the state. The third indicates if this
-- data type is an enumeration (only empty constructors). The third
-- is a function for parsing the recursive positions. A JSON value
-- is then parsed to either a functor, or a failure.
gparseJSONf :: Settings -> Bool -> Bool -> Bool -> StateT [Value] Parser (f a)
-- Void: Used for data types without constructors
-- instance GJSON V1
-- Unit: Used for constructors without arguments
instance GtoJson U1 where
gtoJSONf _ _ _ U1 = Right []
instance GfromJson U1 where
gparseJSONf _ _ _ _ = return U1
-- | Convert any datatype with a 'Generic' instance to a JSON 'Value'.
gtoJson
:: forall a. (Generic a, GtoJson (Rep a), ConNames (Rep a), GIsEnum (Rep a))
=> a -> Value
gtoJson = gtoJsonWithSettings defaultSettings
gtoJsonWithSettings
:: forall a. (Generic a, GtoJson (Rep a), ConNames (Rep a), GIsEnum (Rep a))
=> Settings -> a -> Value
gtoJsonWithSettings settings x =
case gtoJSONf settings (multipleConstructors $ conNames x) (isEnum (Proxy :: Proxy a)) (from x) of
Left [v] -> v
Left _ -> error "The impossible happened: multiple returned values in gtoJSON."
Right _ -> error "The impossible happened: labeled values returned in gtoJSON."
-- | Parse any datatype with a 'Generic' instance from a JSON 'Value'.
gparseJson
:: forall a. (Generic a, GfromJson (Rep a), ConNames (Rep a), GIsEnum (Rep a))
=> Value -> Parser a
gparseJson = gparseJsonWithSettings defaultSettings
gparseJsonWithSettings
:: forall a. (Generic a, GfromJson (Rep a), ConNames (Rep a), GIsEnum (Rep a))
=> Settings -> Value -> Parser a
gparseJsonWithSettings set
= fmap to
. evalStateT (gparseJSONf set (multipleConstructors $ conNames (undefined :: a)) False (isEnum (Proxy :: Proxy a)))
. return
-- Structure type for constant values.
instance ToJSON c => GtoJson (K1 a c) where
gtoJSONf _ _ _ (K1 a) = Left [toJSON a]
instance FromJSON c => GfromJson (K1 a c) where
gparseJSONf _ _ _ _ = lift . fmap K1 . parseJSON =<< pop
instance (GtoJson f, GtoJson g) => GtoJson (f :+: g) where
gtoJSONf set mc enm (L1 x) = gtoJSONf set mc enm x
gtoJSONf set mc enm (R1 x) = gtoJSONf set mc enm x
instance (GfromJson f, GfromJson g) => GfromJson (f :+: g) where
gparseJSONf set mc smf enm
= L1 <$> gparseJSONf set mc smf enm
<|> R1 <$> gparseJSONf set mc smf enm
instance (GtoJson f, GtoJson g) => GtoJson (f :*: g) where
gtoJSONf set mc enm (x :*: y) =
case (gtoJSONf set mc enm x, gtoJSONf set mc enm y) of
(Left xvs, Left yvs) -> Left (xvs ++ yvs)
(Right xvs, Right yvs) -> Right (xvs ++ yvs)
_ -> error "The impossible happened: product of mixed label and non-label fields in GJSON instance for (:*:)."
instance (GfromJson f, GfromJson g) => GfromJson (f :*: g) where
gparseJSONf set mc smf enm =
do unless smf selFields
(:*:) <$> gparseJSONf set mc True enm <*> gparseJSONf set mc True enm
where
selFields =
do v <- pop
case v of
o@Object{} -> put (repeat o)
Array vs -> put (V.toList vs)
_ -> fail "Expected object or array in gparseJSONf for (:*:)."
instance GtoJson f => GtoJson (M1 D c f) where
gtoJSONf set a b (M1 x) = gtoJSONf set a b x
instance GfromJson f => GfromJson (M1 D c f) where
gparseJSONf set a b x = M1 <$> gparseJSONf set a b x
instance (Constructor c, GtoJson f) => GtoJson (M1 C c f) where
gtoJSONf set _ True (M1 _) = Left [toJSON $ conNameT set (undefined :: M1 C c f p)]
gtoJSONf set mc False (M1 x) =
case gtoJSONf set mc False x of
-- Single field constructors are not wrapped in an array.
Left [v] -> Left [wrap v]
Left vs -> Left [wrap . Array $ V.fromList vs]
Right vs -> Left [wrap $ toObject vs]
where
wrap = if mc
then toObject
. return
. (conNameT set (undefined :: M1 C c f p), )
else id
instance (Constructor c, GfromJson f) => GfromJson (M1 C c f) where
gparseJSONf set mc smf True =
do str <- pop
conStr <- lift (parseJSON str)
let expectedConStr = conNameT set (undefined :: M1 C c f p)
unless (conStr == expectedConStr) $
fail $ "Error parsing enumeration: expected " ++ T.unpack expectedConStr ++ ", found " ++ T.unpack conStr ++ "."
M1 <$> gparseJSONf set mc smf True
gparseJSONf set mc smf False =
do
when mc (selProp "C" propName)
M1 <$> gparseJSONf set mc smf False
where
propName = case conNameT set (undefined :: M1 C c f p) of
"" -> Nothing
n -> Just n
instance (Selector c, GtoJson f) => GtoJson (M1 S c f) where
gtoJSONf set mc enm (M1 x) =
case gtoJSONf set mc enm x of
Left [v] -> case selNameT set (undefined :: M1 S c f p) of
Nothing -> Left [v]
Just n -> Right [(n, v)]
Left _ -> error "The impossible happened: multiple returned values inside label in GJSON instance for S."
Right _ -> error "The impossible happened: label inside a label in GJSON instance for S."
instance (Selector c, GfromJson f) => GfromJson (M1 S c f) where
gparseJSONf set mc smf enm =
do selProp "S" propName
M1 <$> gparseJSONf set mc smf enm
where
propName = selNameT set (undefined :: M1 S c f p)
instance (Selector c, ToJSON a) => GtoJson (M1 S c (K1 i (Maybe a))) where
gtoJSONf set _ _ (M1 (K1 n@Nothing)) = case selNameT set (undefined :: M1 S c f p) of
Nothing -> Left [toJSON n]
Just _ -> Right []
gtoJSONf set mc enm (M1 (K1 (Just x))) = gtoJSONf set mc enm (M1 (K1 x) :: (M1 S c (K1 i a)) p)
instance (Selector c, FromJSON a) => GfromJson (M1 S c (K1 i (Maybe a))) where
gparseJSONf set mc smf enm =
do M1 (K1 x) <- parser
return (M1 (K1 (Just x)))
<|>
do case selNameT set (undefined :: M1 S c (K1 i a) p) of
Nothing ->
do o <- pop
M1 . K1 <$> lift (parseJSON o)
Just n ->
do o <- pop
case o of
Object h | H.member n h -> error impossible <$> parser
| otherwise -> return $ M1 (K1 Nothing)
_ -> lift $ typeMismatch "Object" (Array V.empty)
where
parser = gparseJSONf set mc smf enm :: StateT [Value] Parser (M1 S c (K1 i a) p)
impossible = "The impossible happened: parser succeeded after failing in GfromJson S Maybe"
selProp :: Text -> Maybe Text -> StateT [Value] Parser ()
selProp cname propName =
case propName of
Nothing -> do o <- pop
modify (o:)
Just p -> do o <- pop
v <- lift (withObject ("Expected property " ++ show propName ++ " in object in gparseJSONf for " ++ show cname ++ ".")
(.: p) o)
modify (v:)
pop :: MonadState [Value] m => m Value
pop =
do (v:vs) <- get
put vs
return v
toObject :: ToJSON v => [(Text, v)] -> Value
toObject = object . map (uncurry (.=))