关于 json:Aeson:派生结构的一些(但不是全部)字段

Aeson: derive some (but not all) fields of a struct

我有一个大型结构,我需要它是 FromJSON 的一个实例,以便我可以将我的 json 数据解析到其中。

我想自动派生,但是单个字段需要"特别注意",因为它是 json 中的一个对象,我希望它是我的结构中的值的数组。在不编写重复所有字段的巨大 FromJson 实现的情况下如何做到这一点?

示例 json:

1
{"myobject": {"one": 1,"two": 2}, ...many_more_fields...}

示例结构:

1
2
3
4
data MyStruct = MyStruct {
  myobject :: [Int],
  ...many_more_fields,...
} deriving (Generic)

我该如何优雅地做到这一点?


你应该为你的特殊领域创建一个 newtype

1
2
3
4
5
6
7
8
newtype MySpecialType = MySpecialType [Int]

instance FromJSON MySpecialType where ....

data MyStruct = MyStruct {
      myobject:: MySpecialType,
      ...
   }

现在 MyStruct 的实例变得完全正常并且可以以正常方式移交给 Template Haskell。


为了避免在整个代码库中使用 Paul Johnson 非常好的答案中的 newtype,您还可以将您的类型概括如下,将 myobject 的类型作为参数:

1
2
3
4
5
6
7
8
9
10
data MyStruct_ intList = MyStruct {
  myobject :: intlist,
  ...
} deriving (Functor, Generic)

type MyStruct = MyStruct [Int]

instance FromJSON MyStruct where
  parseJSON = (fmap . fmap) (\\(MySpecialType i) -> i)
            . genericParseJSON defaultOptions

上面的

genericParseJSONMyStruct MySpecialType 实例化,然后该字段通过 fmap 展开(注意 MyStruct_Functor)

我也刚刚写了一篇关于"类型手术"的博文,适用于这种问题,这样你就可以保持原来的类型不被修改。

generic-data-surgery 库可以派生出与上述MyStruct_ MySpecialType具有相同Generic结构的泛型类型,供aeson的genericParseJSON使用。手术 modifyRField 然后将函数 \\(MySpecialType i) -> i 应用于 myobject 字段,最终产生 MyStruct.

1
2
3
4
5
6
7
8
9
10
11
import Generic.Data.Surgery (fromOR, toOR', modifyRField)

-- The original type
data MyStruct = MyStruct {
  myobject :: [Int],
  ...
} deriving (Generic)

instance FromJSON MyStruct where
  parseJSON = fmap (fromOR . modifyRField @"myobject" (\\(MySpecialType i) -> i) . toOR')
            . genericParseJSON defaultOptions