提取 Aeson 对象内的嵌套 属性

Extract nested property inside Aeson object

如何使用 Data.Aeson 获得嵌套的 属性?

例如,当使用 Value 解码任意 JSON 字符串时,如下所示:

decode "{\"foo\":{\"bar0\":\"foobar0\",
                  \"bar1\":\"foobar1\"}}" :: Maybe Value

我最终得到这个:

Just (Object (fromList [("foo",Object (fromList [("bar1",String "foobar1"),("bar0",String "foobar0")]))]))

现在,我如何编写一个函数 [String] -> Object -> Maybe Value 来提取 Value,如果有的话,按照提供的属性列表到达?

这个函数应该这样使用:

extractProperty ["foo", "bar0"] (Object (fromList [("foo",Object (fromList [("bar1",String "foobar1"),("bar0",String "foobar0")]))]))

==> Just (String "foobar0")

extractProperty ["foo", "bar0", "baz"] (Object (fromList [("foo",Object (fromList [("bar1",String "foobar1"),("bar0",String "foobar0")]))]))

==> Nothing

以下解决方案使用了 lenslens-aeson 包:

{-# LANGUAGE FlexibleInstances #-}

import Control.Lens (view,pre,ix)        -- from lens
import Control.Lens.Reified (ReifiedTraversal(..))
import Data.Aeson           -- from aeson
import Data.Aeson.Lens (_Object)  -- from lens-aeson
import Data.Text            -- form text

instance Monoid (ReifiedTraversal s s s s) where
    mempty = Traversal id
    mappend (Traversal t1) (Traversal t2) = Traversal (t1 . t2) 

extractProperty :: [Text] -> Object -> Maybe Value 
extractProperty keys o = 
    view (pre telescope) (Object o)
  where
    telescope = runTraversal $ foldMap (\k -> Traversal $ _Object . ix k) keys

ReifiedTraversal 只是围绕 Traversal 的新类型,我们在其上定义了一个 Monoid 实例,以便轻松组合以相同类型开始和结束的遍历(类似于Endo 幺半群是如何工作的)。

在我们的例子中,遍历 _Object . ix kValueValueix 来自 Control.Lens.At 并在 Object.

的属性图上建立索引

我们使用 pre 函数提取组合遍历的第一个结果(如果存在)。

编辑: 正如@cchalmers 在他的评论中提到的,没有必要声明一个孤儿实例,它只适用于 Endo。另外 key k_Object . ix k.

相同

这是 extractProperty 的一个版本,它不使用 lens,而是依赖于使用 foldr 组成 kleisli 箭头列表 Value -> Maybe Value:

import qualified Data.HashMap.Strict as HM

extractProperty :: [T.Text] -> Object -> Maybe Value 
extractProperty keys o = telescope keys (Object o)
  where
    telescope = foldr (>=>) return . map maybeKey 
    maybeKey k v = case v of 
        Object o -> HM.lookup k o  
        _ -> Nothing 

也许 lens 在这种情况下有点矫枉过正。

另一种方法,使用单子绑定:

import Data.Text (Text)
import qualified Data.HashMap.Strict as HM

extractProperty :: [Text] -> Value -> Maybe Value
extractProperty []     v          = Just v
extractProperty (k:ks) (Object o) = HM.lookup k o >>= prop ks
extractProperty _      _          = Nothing