我一直在与Aeson和镜头包(lens-aeson,从核心镜头包迁移而来)一起玩,并且一直在使他们一起工作。
作为一个玩具示例,我有一个类型:
data Colour = Yellow | Green | Blue
和FromJSON实例:
instance FromJSON Colour where
parseJSON (String s) = return $ case s of
"blue" -> Blue
"green" -> Green
_ -> Yellow
parseJSON _ = mzero
到现在为止还挺好。
现在,说我有一些嵌套的JSON数据要从中提取出来:
{
"info": {
"colour": "yellow"
},
/* other props */
}
我不在乎其余的,只在乎这个“颜色”值。更糟糕的是,可以说JSON并不是特别一致,所以有时我有
{ "item": { "colour": "yellow" } }
和其他时间
{ "random": {"item_colour": "yellow"} }
我希望能够尽可能容易地获得颜色值,然后理想地使用我的FromJSON实例将其解析为一种颜色。这是一个玩具示例,但是数据类型可能具有许多字段,而不是颜色。
我开始看镜头原因的东西,这使我寄予了厚望。它允许非常容易地窥视到JSON结构中。例子:
> "{ \"info\": { \"colour\": \"yellow\" } }" ^? key "info" . key "colour"
Just (String "yellow")
> "{ \"info\": { \"colour\": \"yellow\" } }" ^? key "info" . key "colour" . _String
Just "yellow"
但是我找不到通过parseJSON调用来返回
Just Yellow
的方法。 parseJSON似乎很接近,因为它采用了正确的类型(可能至少是这种类型),但之后又崩溃了。理想情况下,我可以执行以下操作之一:> "{ \"info\": { \"colour\": \"yellow\" } }" ^? key "info" . key "colour" . _ParseJSON :: Maybe Colour
Just Yellow
> "{ \"info\": { \"colour\": \"yellow\" } }" ^? key "info" . key "colour" . _Colour
Just Yellow
我发现最接近的是重新编码,然后解码上述结果,例如:
> encode $ "{ \"info\": { \"colour\": \"yellow\" } }" ^? key "info" . key "colour"
"\"yellow\""
这给了我想要的JSON编码数据。在更复杂的情况下,如果该数据是对象或数组,则可以像往常一样取回更复杂的类型,然后在其上运行
decode
,但是解码不喜欢不正确的JSON;用数组或对象语法包装的东西。另外,先进行解码再进行编码似乎非常困惑,并且性能不高。我对镜头和整个Aeson还是比较陌生的(就此而言,还包括Haskell,尽管我逐渐了解了像monads/applicatives这样的东西,所以进展缓慢!)。你们将如何完成这项工作?
我的主要动机是我将处理JSON数据的负载,但实际上只关心它的片段,因此宁愿避免每次我需要从JSON的不同位置获取这些片段时都声明数据类型,而是只声明我关心的位的类型。
请注意,我使用的是lens-aeson-1和lens-4.4.0.1,而不是aeson-lens,它们的工作原理略有不同(可能与答案有关)!
提前致谢!
詹姆士
最佳答案
您可以使用 _JSON
棱镜。您只需要为ToJSON
编写一个Colour
实例:
instance ToJSON Colour where
toJSON Yellow = String "yellow"
toJSON Blue = String "blue"
toJSON Green = String "green"
然后您可以像这样使用它:
parseColour :: String -> Maybe Colour
parseColour j = j ^? key "info" . key "colour" . _JSON
-- point-free: parseColor = preview $ key "info" . key "colour" . _JSON
-- In GHCi
λ: parseColour "{ \"info\": { \"colour\": \"yellow\" } }"
Just Yellow