Type-level default fields for aeson Generic FromJSON parser.
Define default values for missing FromJSON object fields within field type declaration.
It becomes handy e.g. for parsing yaml configuration files. The default values are mostly defined directly at the type level so that the user doesn't have to write manual FromJSON instance.
aeson-generic-default
Type-level configuration of missing values for aeson FromJSON Generic parser.
Using higher-kinded-types, Coercible and Generic instances it is possible to define a class that can be configured by a type parameter to use custom field parsers during parsing and only when the parsing is done, it can be coerced to use normal types.
data ConfigFileT d = ConfigFile {
defaultEnabled :: DefaultField d (DefBool True)
, defaultDisabled :: DefaultField d (DefBool False)
, defaultText :: DefaultField d (DefText "default text")
, defaultInt :: DefaultField d (DefInt 42)
, defaultNegativeInt :: DefaultField d (DefNegativeInt 42)
, defaultRed :: DefaultField d (DefDefault Color)
, defaultBlue :: DefaultField d (DefDefaultConstant BlueDefault)
, normalField :: T.Text
, normalOptional :: Maybe Int
} deriving (Generic)
type ConfigFile = ConfigFileT Final
instance FromJSON ConfigFile where
parseJSON = parseWithDefaults defaultOptions
The resulting ConfigFile
type alias has a form of:
{
defaultEnabled :: Bool
, defaultDisabled :: Bool
, defaultText :: Text
, defaultInt :: Int
, defaultNegativeInt :: Int
, defaultRed :: Color
, defaultBlue :: Color
, normalField :: T.Text
, normalOptional :: Maybe Int
}
The type-level configuration can be easily extended with newtypes, e.g. to create a full compatibility layer with singletons
:
newtype DefSing (a :: k) = DefSing (Demote k) deriving Generic
instance (SingI a, SingKind k, FromJSON (Demote k)) => FromJSON (DefSing (a :: k)) where
omittedField = Just $ DefSing $ fromSing (sing @a)
parseJSON v = DefSing <$> parseJSON v
And then the definition of the field can be for any singleton (e.g. Bool):
flag :: DefaultField d (DefSing True)