Пакет lens
имеет полезные функции для проверки древовидных структур, как JSON Value
с. Также есть пакет lens-aeson
с дополнительными функциями JSON.
import Data.Text
import Data.Aeson
import Data.Aeson.Lens (_Value,_String) -- this is from lens-aeson
import Data.Foldable (toList)
import Control.Lens (Fold,folding,universeOf,toListOf,paraOf,preview)
Мы можем начать с определения линзы Fold
, извлекающий немедленного ребенка Values
данного JSON Value
:
vchildren :: Fold Value Value
vchildren = folding $ \v -> case v of
Object o -> toList o
Array a -> toList a
_ -> []
folding
является функцией от lens
, что создает Fold
из функции, возвращающего список. Список Value
s, в нашем случае.
Мы можем объединить vchildren
с universeOf
функцией от Control.Lens.Plated
, чтобы получить функцию, которая извлекает все транзитивные потомки Value
, в том числе себя:
allValues :: Value -> [Value]
allValues = universeOf vchildren
И эта функция извлекает все тексты, содержащиеся в Value
. Он использует _String
призму из Data.Aeson.Lens
(а Prism
немного, как «овеществленная» шаблон, который может быть передан вокруг):
allTexts :: Value -> [Text]
allTexts = toListOf (folding allValues . _String)
Control.Lens.Plated
также имеет интересные функции, такие как paraOf
, которые позволяют строить «paramorphims». Параморфизм - это «контролируемое разрушение» древовидной структуры, исходящей из листьев, и построение результатов вверх. Например, эта функция
vpara :: (Value -> [r] -> r) -> Value -> r
vpara = paraOf vchildren
принимает в качестве первого параметра другой функции, которая принимает «текущий узел» вместе с промежуточными результатами для приведенных ниже узлов, и создает промежуточный результат для текущего узла.
vpara
начнет потреблять значение JSON из листьев (промежуточный список результатов для этих узлов - просто []
) и продолжается вверх.
Один из вариантов использования vpara
является получение списка дорожек в формате JSON, которые заканчиваются в тексте, который соответствует некоторому условию, например:
type Path = [Value]
pathsThatEndInText :: (Text -> Bool) -> Value -> [Path]
pathsThatEndInText pred = vpara func
where
func :: Value -> [[Path]] -> [Path]
func [email protected](String txt) _ | pred txt = [[v]]
func v [email protected](_:_) = Prelude.map (v:) (Prelude.concat l)
func _ _ = []
Чтобы получить несколько читаемого описание одного из путей, возвращаемых по pathsThatEndInText
:
import qualified Data.HashMap.Strict as HM
import qualified Data.Vector as V
describePath :: Path -> [String]
describePath (v:vs) = Prelude.zipWith step (v:vs) vs
where
step (Object o) next = (unpack . Prelude.head . HM.keys . HM.filter (==next)) o
step (Array a) next = (show . maybe (error "not found") id) (V.elemIndex next a)
step _ _ = error "should not happen"
Наконец, вот пример JSON значение для тестирования вышеуказанных функций в GHCI:
exampleJSON :: Value
exampleJSON = maybe Null id (preview _Value str)
where
str = "[{ \"k1\" : \"aaa\" },{ \"k2\" : \"ccc\" }, { \"k3\" : \"ddd\" }]"
И вот gist.
Возможно, что-то вроде этого '' find_this "' isInfixOf' $ show bt "?? – Sibi
Вы знаете структуру данных, прежде чем разбирать ее? –
@JustinWood Нет. Это большой объект, о котором я ничего не знаю. Некоторые элементы имеют контент, который я ожидаю, и меня интересует только их соседство. Я могу «grep» их, но я надеюсь, что GHCI - лучший инструмент для поиска своего пути, чем текстовый редактор, после довольно печатного контента. – sevo