3

I need to parse an object that has a string element where the string itself is a stringified object:

{ 
  "a"   : "apples",
  "bar" : "{\"b\":\"bananas\"}"
}

I would like to parse this into Just ( Foo { fooA = "apples", fooBar = Bar { barB = "bananas" } } ) so if parsing of bar returns Nothing then the parsing of the whole object returns Nothing, ie the result is as though the bar element of the object was not stringified.

Here is my attempt in which the parsing of testData returns Nothing:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.Aeson  
import Data.Aeson.Types

data Foo = Foo { fooA :: String
               , fooBar :: Bar
               } deriving (Show)

instance FromJSON Foo where
  parseJSON (Object o) = do bar <- (o .: "bar")
                            Foo <$> o .: "a" <*> parseJSON bar
  parseJSON x          = typeMismatch "Foo" x

data Bar = Bar { barB :: String
               } deriving (Show)

instance FromJSON Bar where
  parseJSON (Object o) = Bar <$> o .: "b"
  parseJSON x          = typeMismatch "Bar" x

testData = "{ \"a\":\"apples\", \"bar\":\"{\\\"b\\\":\\\"bananas\\\"}\" }"

main :: IO ()
main = putStrLn $ show d
  where d :: Maybe Foo
        d = decode testData

How can I modify the above code to perform closer to what I need?

helpwithhaskell
  • 558
  • 4
  • 13

1 Answers1

3

You can get more insight into what's going on by using:

main = print (eitherDecode testData :: Either String Foo)

which displays: Left "Error in $: expected Bar, encountered String"

In this code:

 parseJSON (Object o) = do bar <- (o .: "bar")
                           Foo <$> o .: "a" <*> parseJSON bar

bar is String ... value.

To accomplish what you want to do, you could add a case to the FromJSON instance for Bar to catch this:

instance FromJSON Bar where
  ...
  parseJSON (String text) =
    case eitherDecode (textToLBS text) of
      Left  e -> fail $ "while decoding a Bar: " ++ e
      Right b -> return b
  ...

Or you could put this code in the parseJSON definition for Foo.

Here textToLBS converts strict Text to lazy ByteStrings:

import qualified Data.Text as T
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text.Encoding as TE

textToLBS :: T.Text -> LBS.ByteString
textToLBS t = LBS.fromStrict (TE.encodeUtf8 t)

Code available at: http://lpaste.net/143183

ErikR
  • 51,541
  • 9
  • 73
  • 124