我使用 Aeson 接受 JSON 格式的用户配置,其中某些字段可能会被省略,并且将使用默认值。根据doc我应该写这样的东西:
import Data.Aeson
import GHC.Generics
data Person = Person
{ name :: String
, age :: Int
} deriving (Generic, Show)
instance FromJSON Person where
omittedField = Just $ Person "Unnamed" (-1)
parseJSON = genericParseJSON defaultOptions { allowOmittedFields = True }
main :: IO ()
main = do print (eitherDecode @Person "{}")
print (eitherDecode @Person "{\"name\":\"Bob\"}")
print (eitherDecode @Person "{\"name\":\"Bob\",\"age\":42}")
实际上不起作用:
Left "Error in $: parsing Main.Person(Person) failed, key \"name\" not found"
Left "Error in $: parsing Main.Person(Person) failed, key \"age\" not found"
Right (Person {name = "Bob", age = 42})
我相信
omittedField
实例中的 FromJSON
定义适用于 Person
类型的较大数据结构中的字段,而不是 Person
的字段。
一种解决方案是将姓名和年龄设为自己的类型,并为这些类型定义
omittedField
。
newtype Name = Name String
deriving (Generic)
instance FromJSON Name where
omittedField = Just (Name "Unnamed")
parseJSON = genericParseJSON defaultOptions
但是,您也可以将
Name "Unnamed"
的字段包装在 Age (-1)
中,而不是使用任意值 Person
和 Maybe
来表示缺少值。
data Person = Person
{ name :: Maybe String
, age :: Maybe Int
} deriving (Generic, Show)
如果您需要在代码的某些部分使用带有
Person
包装字段的部分定义 Maybe
和在其他地方使用普通字段的完全定义 Person
,您可以同时定义它们。
data PersonMaybe = PersonMaybe
{ name :: Maybe String
, age :: Maybe Int
}
data Person = Person
{ name :: String
, age :: Int
}
然后如果需要的话可以直接转换
PersonMaybe -> Maybe Person
。 “更高种类的数据”(HKD) 模式可以帮助节省一些重复,但它有点更高级,通常不值得,除非您有很多字段并且不仅仅是 2 个状态。
{-# Language TypeFamilies #-}
type family Field f a where
Field Identity a = a
Field f a = f a
data PersonOf f = PersonOf
{ name :: Field f String
, age :: Field f Int
}
type PersonMaybe = PersonOf Maybe
type Person = PersonOf Identity