4
\$\begingroup\$

I'm trying to reduce nested JSON data using Haskell and Aeson without creating data/record types.
The following code works, but seems ugly and unreadable.
Any advice on cleaning this up would be great.

Thank you

import GHC.Exts
import Data.Maybe (fromJust)
import Data.Text (Text)
import Data.Aeson
import qualified Data.HashMap.Strict as M
testVal :: Value
testVal = Object $ fromList [
 ("items", Array $ fromList [ 
 (Object $ fromList [
 ("entity", (Object $ fromList [ 
 ("uuid", String "needed-value1")]))]),
 (Object $ fromList [
 ("entity", (Object $ fromList [ 
 ("uuid", String "needed-value2")]))])
 ])]
getItems :: Value -> Value
getItems (Object o) = fromJust $ M.lookup "items" o
getEntities :: Value -> Value
getEntities (Array a) = Array $ fmap (\(Object o) -> fromJust $ M.lookup "entity" o) a
getUuids :: Value -> Value
getUuids (Array a) = Array $ fmap (\(Object o) -> fromJust $ M.lookup "uuid" o) a
getTexts :: Value -> [Text]
getTexts (Array a) = toList $ fmap (\(String s) -> s) a
someFunc :: IO ()
someFunc = do
 let items = getItems testVal
 entities = getEntities items
 uuids = getUuids entities
 texts = getTexts uuids
 print texts

output:

["needed-value1","needed-value2"]
asked Apr 14, 2020 at 21:57
\$\endgroup\$
1
  • 1
    \$\begingroup\$ You should really use an algebraic data type to implement your JSON interface. Then you can automatically derive FromJSON instances. This will make your code way cleaner. Could you also provide a sample JSON string that you want to parse? \$\endgroup\$ Commented Apr 15, 2020 at 10:46

2 Answers 2

1
\$\begingroup\$

You should strongly consider @Erich's advice and define datatypes but it is possible to write quick and dirty queris without them.

{-# LANGUAGE OverloadedStrings #-} 
{-# LANGUAGE QuasiQuotes #-}
import Data.Text (Text)
import Data.Aeson (Value, Result(..), decode, withObject, (.:))
import Data.Aeson.Types (parse)
import Data.Aeson.QQ.Simple
import Control.Monad ((>=>))
testVal :: Value
testVal = [aesonQQ|
 {"items": [
 {"entity": {"uuid": "needed-value1"}},
 {"entity": {"uuid": "needed-value2"}}
 ]}
|]
getUuids :: Value -> Result [Text]
getUuids = parse $ withObject ""
 $ (.: "items") >=> mapM ((.: "entity") >=> (.: "uuid"))
main :: IO ()
main = do
 print $ getUuids testVal
 print $ getUuids <$> decode "{}"
 print $ getUuids <$> decode "{"

Another option is to use lens-aeson package:

import Control.Lens
import Data.Aeson.Lens (values, key, _String)
-- [...]
main :: IO ()
main = print
 $ testVal ^.. key "items" . values . key "entity" . key "uuid" . _String
answered Apr 15, 2020 at 16:59
\$\endgroup\$
2
\$\begingroup\$

You should really use an algebraic data type to implement your JSON interface. You can even automatically derive FromJSON instances with DeriveGeneric GHC extension, e.g.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import GHC.Generics
import Data.Aeson
import Data.ByteString.Lazy (ByteString)
data Query = Query { items :: [Entity] }
 deriving (Show, Generic)
instance FromJSON Query -- automatically derived instance by DeriveGeneric
data Entity = Entity { uuid :: String }
 deriving (Show)
instance FromJSON Entity where
 parseJSON = withObject "Entity" $ \v -> do
 -- as your json data is additionally nested with an entity object
 -- extract the entity object first
 obj <- v .: "entity"
 -- then extract the uuid field from the entity object
 uid <- obj .: "uuid"
 return $ Entity uid
testVal :: ByteString
testVal = "{\"items\": [{\"entity\": {\"uuid\": \"needed-value1\"}}, {\"entity\": {\"uuid\": \"needed-value2\"}}]}"
main :: IO ()
main = do
 let mayQuery = decode testVal
 case mayQuery of
 Just query -> print $ map uuid $ items query
 Nothing -> putStrLn "JSON parsing error"

I transformed your sample Value into a JSON string, to make the parsing clearer.

The FromJSON instance for Query is automatically derived, if you want to write it by hand, you can do this in analogy to the FromJSON instance of Entity.

This way of parsing your JSON data is very scalable, as you can easily add new fields to your data types, without complicating your code.

answered Apr 15, 2020 at 11:09
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.