Haskell:重复使用带镜头,镜头 - aeson和嵌套JSON的FromJSON实例

时间:2014-08-26 11:02:07

标签: haskell lens aeson

我一直在玩Aeson和镜头包(镜头 - 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实例理想地解析为颜色。这是一个玩具示例,但数据类型可能包含许多字段等,而不是Color。

我开始关注镜头 - aeson的东西,这让我有了希望;它允许非常容易地窥视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,而只是声明我关心的位的类型。

请注意,我使用的是镜头-aeson-1和镜头-4.4.0.1,而不是aeson-lens,它的工作方式略有不同(可能与答案有关)!

提前致谢! 詹姆斯

1 个答案:

答案 0 :(得分:7)

您可以使用_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