2

I have the following code:

{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}

import Data.Aeson
import GHC.Generics

data CharClass = Fighter | Rogue | Wizard deriving (Generic, Show)

instance FromJSON CharClass
instance ToJSON CharClass

I can encode values of this type:

*Main> encode Fighter
"\"Fighter\""

But round-tripping doesn't work:

*Main> eitherDecode $ encode Fighter
Left "Failed reading: satisfy"
*Main> :t eitherDecode $ encode Fighter
eitherDecode $ encode Fighter :: FromJSON a => Either String a

It looks a lot like this answered question, but adding the expected type doesn't work:

*Main> eitherDecode $ encode Fighter :: Either String CharClass 
Left "Failed reading: satisfy"

Interestingly, it does work for fromJSON/toJSON:

*Main> fromJSON $ toJSON Fighter :: Result CharClass
Success Fighter

Making at least one of the constructors non-nullary also makes things work, like if I do this:

data CharClass = Fighter Int | Rogue | Wizard deriving (Generic, Show)

And then try to round-trip again:

*Main> decode $ encode (Fighter 0) :: Maybe CharClass
Just (Fighter 0)

I'm sure I'm missing something simple, but attempting to trace this through the generic code made my head spin.

Community
  • 1
  • 1
Jack Kelly
  • 18,264
  • 2
  • 56
  • 81

3 Answers3

3

JSON is fundamentally a collection of key-value pairs, where values can be a few primitive types or another collection of key-value pairs. Nullary types just don't fit in very well with the whole idea of being JSON entities by themselves. However, they work fine when placed within other types that mesh well with the JSON concept.

data F = F { a :: CharClass, b :: CharClass }
    deriving (Generic, Show)

instance FromJSON F
instance ToJSON F

This looks more like the sort of key-value collection JSON was designed for.

*Main> let x = F Fighter Rogue
*Main> x
F {a = Fighter, b = Rogue}
*Main> decode $ encode x :: Maybe F
Just (F {a = Fighter, b = Rogue})
NovaDenizen
  • 5,089
  • 14
  • 28
  • Interesting, but it makes incremental testing annoying when you can't parse your base cases. – Jack Kelly Jul 17 '15 at 03:53
  • 1
    Check the [json spec](http://json.org/). The top-level parsing element is an `object`, but nullary types only fit in as a `value`. The automatically derived parser only knows how to parse an `object`. – NovaDenizen Jul 17 '15 at 04:00
  • 1
    Sure, but check the [docs for aeson](http://hackage.haskell.org/package/aeson-0.9.0.1/docs/Data-Aeson-Parser.html#v:json). "In aeson 0.8 and earlier, it parsed only object or array types, in conformance with the now-obsolete RFC 4627." Looks like it's meant to be able to parse things that aren't objects at the top level? – Jack Kelly Jul 17 '15 at 04:04
  • http://hackage.haskell.org/package/aeson-0.9.0.1/docs/Data-Aeson-Parser.html#v:value – NovaDenizen Jul 17 '15 at 05:25
2

The version of aeson that stack installed on my machine was from the 0.8 series, and in aeson 0.8 or earlier, only objects and arrays were parsed at the root level.

Jack Kelly
  • 18,264
  • 2
  • 56
  • 81
2

In aeson 0.9, decode uses value parser. So nullable object (represented as string) at top-level will work.

For 0.8 the below example works. I don't know why decodeWith isn't exposed.

{-# LANGUAGE DeriveGeneric #-}

import Control.Applicative
import GHC.Generics
import Data.Aeson
import Data.Aeson.Parser
import Data.ByteString.Lazy as L
import Data.Attoparsec.ByteString.Char8 (endOfInput, skipSpace)
import qualified Data.Attoparsec.Lazy as L

data CharClass = Fighter | Rogue | Wizard deriving (Generic, Show)

instance ToJSON CharClass
instance FromJSON CharClass

decodeWith p to s =
    case L.parse p s of
      L.Done _ v -> case to v of
                      Success a -> Just a
                      _         -> Nothing
      _          -> Nothing
{-# INLINE decodeWith #-}

valueEOF = value <* skipSpace <* endOfInput

decodeValue :: (FromJSON a) => L.ByteString -> Maybe a
decodeValue = decodeWith valueEOF fromJSON

main :: IO ()
main = print (decodeValue (encode Fighter) :: Maybe CharClass)
phadej
  • 11,947
  • 41
  • 78