0

I'm trying to parse embedded JSON of the form

{
  "foo":"bar",
  "baz":"\{\"somekey\":\"someval\"\}"
}

with Aeson in Haskell. Here are my types:

data BaseType = BaseType { foo :: String, baz :: EmbeddedType } deriving(Show)

instance FromJSON BaseType where
  parseJSON = withObject "BaseType" $ \o -> do
    foo <- o .: "foo"
    baz <- o .: "baz"
    return $ BaseType { foo=foo, baz=baz }

data EmbeddedType = EmbeddedType { somekey :: String }

instance FromJSON EmbeddedType where
  parseJSON = withObject "EmbeddedType" $ \o -> do
    somekey <- o .: "somekey"
    return $ EmbeddedType {somekey=somekey}

Obviously, the FromJSON instance for BaseType doesn't work, since it sees it as a Value String instead of as more JSON for it to parse. I tried to find a way to use decodeEither in my FromJSON BaseType instance, but that required that I do some black magic to convert from String to ByteString, and I feel like there must be a neater way, possibly related to withEmbeddedJSON.

How can I make this work correctly?

5
  • Isn't this something that can be addressed with the standard DeriveGeneric mechanism of Aeson? Commented Dec 19, 2018 at 16:08
  • @MarkSeemann I'm not sure what that means and google isn't spitting out anything useful for "DeriveGeneric Aeson". Would you mind explaining or linking me somewhere? Commented Dec 19, 2018 at 16:11
  • hackage.haskell.org/package/aeson/docs/Data-Aeson.html Commented Dec 19, 2018 at 16:12
  • @MarkSeemann I don't think this solves my issue. It still gives a "got String expected EmbeddedType" error. Commented Dec 19, 2018 at 16:19
  • 1
    That's not something that is addressed by aeson's deriving stuff. Commented Dec 19, 2018 at 16:32

1 Answer 1

2

I guess it would be something like this (untested):

instance FromJSON BaseType where
  parseJSON = withObject "BaseType" $ \o -> do
    foo <- o .: "foo"
    bazText <- o .: "baz"
    baz <- withEmbeddedJSON "EmbeddedType" parseJSON (String bazText)
    return $ BaseType { foo=foo, baz=baz }

Or you could consider moving the call to withEmbeddedJSON into the EmbeddedType instance; then o .: "baz" should Just Work in the BaseType instance, at the cost of no longer having a handle onto a parser that just does EmbeddedType parsing without de-stringifying:

instance FromJSON BaseType where
  parseJSON = withObject "BaseType" $ \o -> do
    foo <- o .: "foo"
    baz <- o .: "baz"
    return $ BaseType { foo=foo, baz=baz }

instance FromJSON EmbeddedType where
  parseJSON = withEmbeddedJSON "EmbeddedType" . withObject "EmbeddedType" $ \o -> do
    somekey <- o .: "somekey"
    return $ EmbeddedType {somekey=somekey}
Sign up to request clarification or add additional context in comments.

6 Comments

you could do baz_ <- o .: "baz" and give baz_ type Text (aeson does decoding anyway). That would save the case HM.lookup .....
I think you'll have to make an explicit call to decode in any case. (parseJSON still requires you to get a Value from somewhere)
@Li-yaoXia withEmbeddedJSON calls decode for you (well, eitherDecode, anyway).
Thank you for your help, and the code works, but would you mind helping me out a bit by explaining a couple things? I'm kinda new to Haskell, and I don't understand 1) how you're composing withEmbeddedJSON with withObject, 2) Your use of the String type constructor, and 3) How passing parseJSON into withEmbeddedJSON does what we want it to.
@thesecretmaster For (1) you might like this question; for (2) I looked at the source of withEmbeddedJSON to see what it expected as input; for (3) type inference selects parseJSON :: Value -> Parser EmbeddedType, so it dispatches to your other instance for parsing the embedded JSON.
|

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.