Here is my attempt using a small helper parser:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module MyModule where
import Control.Monad
import Data.Aeson
import Data.Aeson.Types
import Data.ByteString (ByteString)
import qualified Data.Text.Encoding as Text
import GHC.Generics
data Address = Address
{ street :: String
, city :: String
, state :: String
} deriving (Generic, Show)
instance FromJSON Address
data Person = Person
{ firstName :: String
, lastName :: String
, address :: Address
} deriving (Generic, Show)
instance FromJSON Person where
parseJSON (Object o) =
Person <$> o .: "firstName" <*> o .: "lastName" <*>
(parseAddress =<< o .: "address")
parseJSON _ = mzero
parseAddress :: Value -> Parser Address
parseAddress (String s) = do
let maybeObject = decodeStrict (Text.encodeUtf8 s)
case maybeObject of
Nothing -> mzero
Just o -> parseJSON (Object o)
parseAddress _ = mzero
testString :: ByteString
testString =
"{\n \"firstName\": \"Frederick\",\n \"lastName\": \"Krueger\",\n\"address\": \"{\\\"street\\\": \\\"Elm Street, 13\\\", \\\"city\\\": \\\"Springwood\\\", \\\"state\\\": \\\"OH\\\"}\"\n}\n"
and running it in a repl:
λ> eitherDecodeStrict testString :: Either String Person
Right (Person {firstName = "Frederick", lastName = "Krueger", address = Address {street = "Elm Street, 13", city = "Springwood", state = "OH"}})
In brief the Person
instance uses =<<
operator to chain two parsers together - the one produced by o .: "address"
and parseAddress
. Within parseAddress
we can then examine the value and do further processing if we see a String
. decodeStrict
is used to attempt decoding the string as an object and once parseAddress
has that parseJSON
can be used to parse that object as an Address
.
Or as a one liner with no intermediate values:
parseAddress :: Value -> Parser Address
parseAddress (String s) =
either (const mzero) parseJSON (eitherDecodeStrict (Text.encodeUtf8 s))
parseAddress _ = mzero