我一直在与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

10-08 12:36