如何使用Haskell Lens遍历JSON并按指定Key实现索引?
使用Haskell Lens按指定键索引JSON数组并关联子元素
最近我在用Lens处理JSON文档时遇到了这样一个需求:我有一个指向JSON的Lens,比如doc ^? ((key "body").values),目标JSON结构是这样的:
{"body": [{"key": 23, "data": [{"foo": 1}, {"foo": 2}]}]}
我希望用每个body元素里的"key"字段值作为索引,关联其data数组里的foo值,最终通过类似这样的表达式得到结果:
doc ^? key "body" . values . indexWith (key "key") . key "data" . values . key "foo" . withIndex
期望输出是[(23, 1), (23, 2)]。
下面是我的最小可复现代码(MVCE):
#!/usr/bin/env stack -- stack --resolver lts-11.7 script {-# LANGUAGE OverloadedStrings #-} import Control.Lens import Data.Aeson import Data.Aeson.Lens main :: IO () main = do let doc = object ["body" .= [object ["key" .= (23 :: Int), "data" .= [object ["foo" .= 1], object ["foo" .= 2]]]]] -- 这里需要实现indexWith,让下面的表达式能得到期望结果 let result = doc ^? key "body" . values . indexWith (key "key") . key "data" . values . key "foo" . withIndex print result
实现方案
要实现这个需求,我们需要自定义一个带索引的遍历器(IndexedTraversal)indexWith,它的作用是提取每个元素的指定字段值作为索引,让后续的遍历操作能携带这个索引信息。
直接上代码实现:
import Control.Lens.Indexed (IndexedTraversal, indexed) -- 接收一个用于提取索引的Lens/Getting,返回带索引的遍历器 indexWith :: Getting a s a -> IndexedTraversal a s s indexWith idx = indexed $ \f s -> f (s ^. idx) s
把这个函数加到MVCE的导入和主函数之间,运行代码就能得到期望的Just [(23,1),(23,2)]了。
替代方案(无需自定义函数)
如果你不想额外定义函数,也可以用现有Lens组合直接写出逻辑:
let result = doc ^? key "body" . values . (\elem -> indexed (\idx _ -> id) (elem ^. key "key") elem) . key "data" . values . key "foo" . withIndex
不过显然自定义indexWith的方式更清晰易读,也方便复用。
逻辑解释
indexWith (key "key"):遍历body数组的每个元素,提取其key字段的值作为索引,把元素包装成带索引的遍历目标。key "data" . values:深入每个元素的data数组,遍历其中的每个子元素。key "foo":提取子元素里的foo值。withIndex:把之前提取的key索引和当前的foo值配对,最终形成我们想要的元组列表。
内容的提问来源于stack exchange,提问作者Reactormonk
相关产品推荐
相关产品推荐

