{-# LANGUAGE ConstraintKinds, DataKinds, DeriveGeneric, DerivingVia #-} {-# LANGUAGE ExplicitNamespaces, FlexibleContexts, FlexibleInstances #-} {-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds, ScopedTypeVariables, StandaloneDeriving #-} {-# LANGUAGE TypeApplications, TypeFamilies, TypeInType, TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wall #-} -- | https://gist.github.com/konn/27c00f784dd883ec2b90eab8bc84a81d module Data.Aeson.Generic.DerivingVia ( StrFun(..), Setting(..), SumEncoding'(..), DefaultOptions, WithOptions(..) , -- Utility type synonyms to save ticks (') before promoted data constructors type Drop, type CamelTo2, type UserDefined , type TaggedObj, type UntaggedVal, type ObjWithSingleField, type TwoElemArr , type FieldLabelModifier , type ConstructorTagModifier , type AllNullaryToStringTag , type OmitNothingFields , type SumEnc , type UnwrapUnaryRecords , type TagSingleConstructors ) where import Prelude import Data.Aeson (FromJSON (..), GFromJSON, GToJSON, ToJSON (..)) import Data.Aeson (Options (..), Zero, camelTo2, genericParseJSON) import Data.Aeson (defaultOptions, genericToJSON) import qualified Data.Aeson as Aeson import Data.Kind (Constraint, Type) import Data.Proxy (Proxy (..)) import Data.Reflection (Reifies (..)) import GHC.Generics (Generic, Rep) import GHC.TypeLits (KnownNat, KnownSymbol, natVal, symbolVal) import GHC.TypeLits (Nat, Symbol) newtype WithOptions options a = WithOptions { runWithOptions :: a } data StrFun = Drop Nat | CamelTo2 Symbol | forall p. UserDefined p type Drop = 'Drop type CamelTo2 = 'CamelTo2 type UserDefined = 'UserDefined type family Demoted a where Demoted Symbol = String Demoted StrFun = String -> String Demoted [a] = [Demoted a] Demoted Setting = Options -> Options Demoted SumEncoding' = Aeson.SumEncoding Demoted a = a data SumEncoding' = TaggedObj {tagFieldName' :: Symbol, contentsFieldName :: Symbol } | UntaggedVal | ObjWithSingleField | TwoElemArr type TaggedObj = 'TaggedObj type UntaggedVal = 'UntaggedVal type ObjWithSingleField = 'ObjWithSingleField type TwoElemArr = 'TwoElemArr data Setting = FieldLabelModifier [StrFun] | ConstructorTagModifier [StrFun] | AllNullaryToStringTag Bool | OmitNothingFields Bool | SumEnc SumEncoding' | UnwrapUnaryRecords Bool | TagSingleConstructors Bool type FieldLabelModifier = 'FieldLabelModifier type ConstructorTagModifier = 'ConstructorTagModifier -- | If 'True' the constructors of a datatype, with all nullary constructors, -- will be encoded to just a string with the constructor tag. If 'False' the -- encoding will always follow the 'SumEncoding'. type AllNullaryToStringTag = 'AllNullaryToStringTag type OmitNothingFields = 'OmitNothingFields type SumEnc = 'SumEnc -- | Hide the field name when a record constructor has only one field, like a -- newtype. type UnwrapUnaryRecords = 'UnwrapUnaryRecords -- | Encode types with a single constructor as sums, so that -- 'AllNullaryToStringTag' and 'SumEncoding' apply. type TagSingleConstructors = 'TagSingleConstructors class Demotable (a :: k) where demote :: proxy a -> Demoted k type All :: (Type -> Constraint) -> [Type] -> Constraint type family All p xs where All p '[] = () All p (x ': xs) = (p x, All p xs) instance Reifies f (String -> String) => Demotable ('UserDefined f) where demote _ = reflect @f Proxy instance KnownSymbol sym => Demotable sym where demote = symbolVal instance (KnownSymbol s, KnownSymbol t) => Demotable ('TaggedObj s t) where demote _ = Aeson.TaggedObject (symbolVal @s Proxy) (symbolVal @t Proxy) instance Demotable 'UntaggedVal where demote _ = Aeson.UntaggedValue instance Demotable 'ObjWithSingleField where demote _ = Aeson.ObjectWithSingleField instance Demotable 'TwoElemArr where demote _ = Aeson.TwoElemArray instance Demotable xs => Demotable ('FieldLabelModifier xs) where demote _ o = o { fieldLabelModifier = foldr (.) id (demote (Proxy @xs)) } instance Demotable xs => Demotable ('ConstructorTagModifier xs) where demote _ o = o { constructorTagModifier = foldr (.) id (demote (Proxy @xs)) } instance Demotable b => Demotable ('AllNullaryToStringTag b) where demote _ o = o { allNullaryToStringTag = demote (Proxy @b) } instance Demotable b => Demotable ('OmitNothingFields b) where demote _ o = o { omitNothingFields = demote (Proxy @b) } instance Demotable b => Demotable ('UnwrapUnaryRecords b) where demote _ o = o { unwrapUnaryRecords = demote (Proxy @b) } instance Demotable b => Demotable ('TagSingleConstructors b) where demote _ o = o { tagSingleConstructors = demote (Proxy @b) } instance Demotable b => Demotable ('SumEnc b) where demote _ o = o { sumEncoding = demote (Proxy @b) } instance Demotable 'True where demote _ = True instance Demotable 'False where demote _ = False instance KnownNat n => Demotable ('Drop n) where demote _ = drop (fromIntegral $ natVal (Proxy :: Proxy n)) instance KnownSymbol sym => Demotable ('CamelTo2 sym) where demote _ = camelTo2 $ head $ symbolVal @sym Proxy instance {-# OVERLAPPING #-} Demotable ('[] :: [k]) where demote _ = [] instance (Demotable (x :: k), Demotable (xs :: [k])) => Demotable (x ': xs) where demote _ = demote (Proxy @x) : demote (Proxy @xs) type DefaultOptions = ('[] :: [Setting]) reflectOptions :: forall xs proxy. Demotable (xs :: [Setting]) => proxy xs -> Options reflectOptions pxy = foldr (.) id (demote pxy) defaultOptions instance (Demotable (options :: [Setting])) => Reifies options Options where reflect = reflectOptions instance (Generic a, GToJSON Zero (Rep a), Reifies (options :: k) Options) => ToJSON (WithOptions options a) where toJSON = genericToJSON (reflect (Proxy @options)) . runWithOptions instance (Generic a, GFromJSON Zero (Rep a), Reifies (options :: k) Options) => FromJSON (WithOptions options a) where parseJSON = fmap WithOptions . genericParseJSON (reflect (Proxy @options))