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.

Bob Jansen
  • 1,215
  • 2
  • 12
  • 31
  • 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. – Thomas M. DuBuisson Dec 08 '18 at 08:35

1 Answers1

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 )        
Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
  • 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. – Bob Jansen Dec 08 '18 at 08:56
  • 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. – Thomas M. DuBuisson Dec 08 '18 at 09:01