使用 Haskell Aeson 设置省略字段的默认值

问题描述 投票:0回答:1

我使用 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})
json haskell aeson
1个回答
0
投票

我相信

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
© www.soinside.com 2019 - 2024. All rights reserved.