Извлечь вложенное свойство внутри объекта Aeson

Как я могу получить вложенное свойство, используя Data.Aeson?

Например, при декодировании произвольной строки JSON с использованием Value следующим образом:

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

person Community    schedule 11.05.2015    source источник


Ответы (2)


В следующем решении используются пакеты lens и lens-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 k идет от Value до Value. ix происходит из Control.Lens.At и индексы на карте свойств Object.

Мы извлекаем первый результат составленного обхода (если он существует) с помощью pre.

Редактировать: Как отмечает @cchalmers в своем комментарии, нет необходимости объявлять потерянный экземпляр, он отлично работает только с Endo. Также key k совпадает с _Object . ix k.

Вот версия extractProperty, которая не использует lens, а вместо этого полагается на составление списка стрелок клейсли Value -> Maybe Value с использованием foldr:

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 было немного лишним.

person danidiaz    schedule 11.05.2015
comment
Почему бы просто не использовать Endo вместо вводящего в заблуждение сироты? Также _Object . ix k = key i. Вот более сжатая версия extract ks = preview (alaf Endo foldMap key ks) . Object. - person cchalmers; 11.05.2015
comment
Это элегантно, но кажется излишним (возможно, потому, что я еще не изучил линзы). Есть ли более простое, возможно, рекурсивное решение? - person ; 11.05.2015
comment
@cchalmers Можете ли вы расширить свои предложения в новом ответе? - person ; 11.05.2015
comment
@cchalmers У меня сложилось ошибочное впечатление, что Endo плохо работает с обходами/линзами. - person danidiaz; 11.05.2015

Другой подход, использующий монадическую привязку:

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
person Łukasz    schedule 05.05.2017