{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Data.Aeson.Options
(
defaultOptions
, leaveTagOptions
, defaultOptionsPS
, stripTypeOptions
, genericParseJSONStripType
, genericToJSONStripType
) where
import Data.Aeson.Types (Parser)
import Data.Char (isLower, isPunctuation, isUpper, toLower)
import Data.List (findIndex, isPrefixOf)
import GHC.Generics (Generic, Rep)
import Type.Reflection (Typeable, typeRep)
import qualified Data.Aeson as A
headToLower :: String -> String
headToLower [] = error "Can not use headToLower on empty String"
headToLower (x:xs) = toLower x : xs
stripFieldPrefix :: String -> String
stripFieldPrefix = dropWhile (not . isUpper)
dropPunctuation :: String -> String
dropPunctuation = filter (not . isPunctuation)
stripConstructorPrefix :: String -> String
stripConstructorPrefix t =
maybe t (flip drop t . decrementSafe) $ findIndex isLower t
where
decrementSafe 0 = 0
decrementSafe i = i - 1
defaultOptions :: A.Options
defaultOptions =
A.defaultOptions
{ A.fieldLabelModifier = headToLower . stripFieldPrefix . dropPunctuation
, A.constructorTagModifier = headToLower . stripConstructorPrefix
, A.sumEncoding = A.ObjectWithSingleField
}
leaveTagOptions :: A.Options
leaveTagOptions = defaultOptions { A.constructorTagModifier = id }
defaultOptionsPS :: A.Options
defaultOptionsPS =
A.defaultOptions
{ A.constructorTagModifier = headToLower . stripConstructorPrefix
}
genericParseJSONStripType
:: forall a .
(Typeable a, Generic a, A.GFromJSON A.Zero (Rep a))
=> A.Value
-> Parser a
genericParseJSONStripType = A.genericParseJSON (stripTypeOptions @a)
genericToJSONStripType
:: forall a .
(Typeable a, Generic a, A.GToJSON A.Zero (Rep a))
=> a
-> A.Value
genericToJSONStripType = A.genericToJSON (stripTypeOptions @a)
stripTypeOptions :: forall a . Typeable a => A.Options
stripTypeOptions = A.defaultOptions
{ A.fieldLabelModifier = stripTypeNamePrefix
}
where
typeName :: String
typeName = headToLower $ show $ typeRep @a
stripTypeNamePrefix :: String -> String
stripTypeNamePrefix fieldName =
if typeName `isPrefixOf` fieldName
then headToLower $ drop (length typeName) fieldName
else fieldName