3

I need to be able to parse a giant nested JSON structure into something more malleable, preferably a map from strings to strings.

Example of the kinds of structures I'm talking about:

{ "foo" : "baz", "bar" : {"qux" : "quux", "baz" : {"abracadabra" : "alakazam", "abc" : "xyz" } } }

What I want to parse the above into:

fromList [("foo", "baz"), ("bar/qux", "quux"), ("bar/baz/abracadabra", "alakazam"), ("bar/baz/abc", "xyz")]

Is it possible to use this with a relatively stock parser from Aeson? Should I just do some monkeying around with the JSON AST that Aeson parses until it works?

1
  • 3
    Yeah I would monkey with the JSON AST, very unlikely to be stock. This is an obvious recursive traversal of type JSON -> String -> Map String String Commented Jun 11, 2014 at 23:07

2 Answers 2

4

I couldn't help myself here, I had to abuse lens...

In the example below Primitive is just a data type of the leaf JSON values.

import Data.Aeson
import Control.Lens
import Data.Aeson.Lens

flatten :: Value -> [(String, Primitive)]
flatten (Object obj) = do
    (k, v) <- obj^@..ifolded
    flatten v <&> _1 <>~ (k^..each ++ "/")
flatten (Array  arr) = arr^..each >>= flatten
flatten prim         = [("", prim^?! _Primitive)]

input = "{ \"foo\" : \"baz\", \"bar\" : {\"qux\" : \"quux\", \"baz\" : {\"abracadabra\" : \"alakazam\", \"abc\" : \"xyz\" } } }"

main = do
    print $ input^? _Value . to flatten
    -- Just [("foo/",StringPrim "baz"),("abc/baz/bar/",StringPrim "xyz"),("abracadabra/baz/bar/",StringPrim "alakazam"),("qux/bar/",StringPrim "quux")]
Sign up to request clarification or add additional context in comments.

1 Comment

As much as I like lens, I really don't think these operators are helping readability in any way, especially if you omit spaces too.
2
module AesonFun(collapse) where

import qualified Data.Map as M (empty, Map, insert, lookup)
import qualified Data.HashMap.Strict as HM (toList)
import qualified Data.List as L (foldl')
import qualified Data.Aeson as Ae (decode, )
import qualified Data.Text as T (unpack, Text)
import Data.Aeson.Types as AT
import qualified Data.ByteString.Lazy.Char8 as BS ( pack )

collapse :: String -> StringMap
collapse s = maybe M.empty (collapser0 M.empty) $ toValue s

toValue :: String -> Maybe Value
toValue = Ae.decode . BS.pack

type StringMap = M.Map String String

delim = "/"

type Collapser = StringMap -> Value -> StringMap

collapser0 = collapser Nothing

collapser :: (Maybe String) -> Collapser
collapser s m v = case v of
                    AT.Object ob  -> L.foldl' (\m' (c,v') -> c m' v')  m pairs where
                      pairs :: [(Collapser, Value)]
                      pairs = map toPair $ HM.toList ob
                      toPair :: (T.Text, Value) -> (Collapser, Value)
                      toPair (t, v') = (collapser s', v') where
                        s' = case s of
                               Just p  -> Just $ p ++ delim ++ (T.unpack t)
                               Nothing -> Just $ (T.unpack t)
                    AT.String t  -> maybe m (\str -> M.insert str (T.unpack t) m) s
                    otherwise    -> m

AesonFun> collapse "{ \"foo\" : \"baz\", \"bar\" : {\"qux\" : \"quux\", \"baz\" : {\"abracadabra\" : \"alakazam\", \"abc\" : \"xyz\" } } }"
fromList [("bar/baz/abc","xyz"),("bar/baz/abracadabra","alakazam"),("bar/qux","quux"),("foo","baz")]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.