Home > Back-end >  How to simplify reading YAML in Haskell
How to simplify reading YAML in Haskell

Time:07-06

If I got a YAML,

canary_instance:
   name: instance_name
   metadata:
      a:
        b:
          c: d

How to easily get value of nested block without too much boilerplate code? e.g. like in Python obj["canary_instance"]["metadata"]["a"]["b"]["c"] without having to write new data type for each key

I have followed this tutorial, and modified to fit the above example

{-# LANGUAGE OverloadedStrings #-}

module Parser where

import Prelude hiding (readFile)
import Data.Aeson
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Yaml as Y



data Instance = Instance
     { name :: String
     , metadata :: InstanceMetadata
     } deriving Show

data InstanceMetadata = InstanceMetadata
     { a :: InstanceMetadataA
     } deriving Show

data InstanceMetadataA = InstanceMetadataA
     { b :: InstanceMetadataB
     } deriving Show

data InstanceMetadataB = InstanceMetadataB
     { c :: String
     } deriving Show

     
instance FromJSON Instance where
         parseJSON (Object m) = Instance <$>
                   m .: "name" <*>
                   m .: "metadata"
         parseJSON x = fail ("not an object: "    show x)

instance FromJSON InstanceMetadata where
         parseJSON (Object m) = InstanceMetadata <$>
                   m .:  "a"
         parseJSON x = fail ("not an object: "    show x)

instance FromJSON InstanceMetadataA where
         parseJSON (Object m) = InstanceMetadataA <$>
                   m .: "b"
         parseJSON x = fail ("not an object: "    show x)

instance FromJSON InstanceMetadataB where
         parseJSON (Object m) = InstanceMetadataB <$>
                   m .: "c"
         parseJSON x = fail ("not an object: "    show x)


main :: IO ()
main = do
     y <- either (error . show) id <$>
            Y.decodeFileEither "example.yaml" :: IO (Map String Instance)
     print y
     print $ fmap (c . b . a . metadata) (Map.lookup "canary_instance" y)

The output is

> :main
fromList [("canary_instance",Instance {name = "instance_name", metadata = InstanceMetadata {a = InstanceMetadataA {b = InstanceMetadataB {c = "d"}}}})]
Just "d"

CodePudding user response:

One way of doing this is to use the lens-aeson package. Here's how you'd modify your program to do that:

{-# LANGUAGE OverloadedStrings #-}

module Parser where

import Prelude hiding (readFile)
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import qualified Data.Yaml as Y



main :: IO ()
main = do
     y <- either (error . show) id <$>
            Y.decodeFileEither "example.yaml" :: IO Value
     print y
     print $ y ^? key "canary_instance" . key "metadata" . key "a" . key "b" . key "c" . _String
  • Related