2

When trying to parse some simple JSON using Aeson I get a type error I don't understand. I have the following JSON

jsonString = "[\"a\", [\"b\", \"c\"]]" :: L.ByteString

and I have defined the following imports and code:

import Data.Aeson
import GHC.Generics
import qualified Data.ByteString.Lazy as L

data Ch = Ch {
   c1 :: String,
   c2 :: (String, String)
} deriving (Show, Generic)
instance FromJSON Ch

When I try to use eitherDecode on this string with my Ch type I get an error

*Aeson> eitherDecode jsonString :: Either String Ch
Left "Error in $: expected record (:*:), encountered Array"

Can someone explain me the error and tell me how I should parse this JSON?

An approach that would work is

eitherDecode jsonString :: Either String (String, (String, String))

but I'd rather go to my type directly.

1
  • That isn't a type error, that's just a runtime error telling you [ "a", [ "b", "c" ] ] json is not the same as the value expected by parser you just defined, { "c1" : "some string", "c2" : ["string", "string"] }. If you want to parse something other than what the generic instance defines then you'll need to define it manually. Commented Dec 8, 2018 at 8:35

1 Answer 1

2

If you already know of a type that parses as intended then perhaps the easiest solution is to just write your instance in terms of that type and translating:

import Data.Aeson
import GHC.Generics
import qualified Data.ByteString.Lazy as L

data Ch = Ch {
   c1 :: String,
   c2 :: (String, String)
} deriving (Show, Generic)

instance FromJSON Ch where
    parseJSON x =
        do (a,(b,c)) <- parseJSON x
           pure (Ch a (b,c))

And the result is:

*Main> :set -XOverloadedStrings
*Main> eitherDecode "[\"a\", [\"b\", \"c\"]]" :: Either String Ch
Right (Ch {c1 = "a", c2 = ("b","c")})

EDIT:

A more direct use of Aeson's API can be informative or preferred:

instance FromJSON Ch where
    parseJSON =
       withArray "Ch" $ \arr ->
       -- from Data.Aeson.Types
           if V.length arr /= 2
              -- ^ from Data.Vector
              then typeMismatch "Length should be 2" (Array arr)
                   -- ^ from Data.Aeson.Types
              else Ch <$> parseJSON (arr ! 0) <*> parseJSON ( arr ! 1 )        
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, this works. My misunderstanding was rooted in the fact that if Ch is defined the parser expects JSON that has keys, right? So, parsing JSON lists to records would always require some manual work.
That's right. The generic instance for basically any serialization-style class is a "do something that is well defined but somewhat arbitrary" and as a result that "something" is often not what you want unless you're interacting with another Haskell process.

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.