Разбор типов данных со всеми нульарными конструкторами с использованием универсального декодирования

У меня есть следующий код:

{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}

import Data.Aeson
import GHC.Generics

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

instance FromJSON CharClass
instance ToJSON CharClass

Я могу кодировать значения этого типа:

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

Но обход не работает:

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

Это очень похоже на ответ на этот вопрос, но добавление ожидаемого типа не работает:

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

Интересно, что это работает для fromJSON/toJSON:

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

Если сделать хотя бы один из конструкторов ненулевым, то все заработает, например, если я сделаю так:

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

А затем снова попробуйте туда-обратно:

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

Я уверен, что упустил что-то простое, но попытка отследить это через общий код заставила меня закружиться.


person Jack Kelly    schedule 17.07.2015    source источник
comment
Вы пытались добавить производный экземпляр Read?   -  person Jeremy List    schedule 17.07.2015
comment
Получение Read не имеет значения, но спасибо за предложение.   -  person Jack Kelly    schedule 17.07.2015
comment
Я не слишком удивлен, но это было первое, что я бы попробовал. К сожалению, у меня нет опыта использования реализации по умолчанию любого метода в классах FromJSON и ToJSON (я всегда реализовывал их вручную, чтобы соответствовать уже существующей схеме)   -  person Jeremy List    schedule 17.07.2015
comment
Я думаю, ты забыл изгнать демонов. Обратитесь к местному духовенству.   -  person dfeuer    schedule 17.07.2015
comment
Вы должны попробовать спросить ghci о типах этих ничего.   -  person dfeuer    schedule 17.07.2015
comment
Кроме того, вы можете попробовать eitherDecode с сигнатурой типа, чтобы проверить несоответствия кодирования/декодирования.   -  person dfeuer    schedule 17.07.2015
comment
Хороший звонок @dfeuer - я добавил к вопросу информацию eitherDecode и :t.   -  person Jack Kelly    schedule 17.07.2015


Ответы (3)


JSON — это, по сути, набор пар ключ-значение, где значения могут быть несколькими примитивными типами или другим набором пар ключ-значение. Нулевые типы просто не очень хорошо вписываются в саму идею быть сущностями JSON. Однако они прекрасно работают, когда помещаются в другие типы, которые хорошо сочетаются с концепцией JSON.

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

instance FromJSON F
instance ToJSON F

Это больше похоже на коллекцию ключей и значений, для которой был разработан JSON.

*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})
person NovaDenizen    schedule 17.07.2015
comment
Интересно, но инкрементное тестирование раздражает, когда вы не можете разобрать свои базовые случаи. - person Jack Kelly; 17.07.2015
comment
Ознакомьтесь с спецификацией json. Элемент синтаксического анализа верхнего уровня — это object, но пустые типы подходят только как value. Автоматически созданный синтаксический анализатор знает только, как анализировать object. - person NovaDenizen; 17.07.2015
comment
Конечно, но проверьте документы для вечности. В aeson 0.8 и более ранних версиях он анализировал только типы объектов или массивов в соответствии с устаревшим RFC 4627. Похоже, что он предназначен для анализа вещей, которые не являются объектами, на верхнем уровне? - person Jack Kelly; 17.07.2015
comment
hackage.haskell.org/ пакет/aeson-0.9.0.1/docs/ - person NovaDenizen; 17.07.2015

Версия aeson, которую stack установила на моем компьютере, относится к серии 0.8, а в aeson 0.8 или более ранней версии на корневом уровне анализировались только объекты и массивы.

person Jack Kelly    schedule 17.07.2015

В aeson 0.9 decode использует парсер value. Таким образом, объект с нулевым значением (представленный в виде строки) на верхнем уровне будет работать.

Для 0.8 работает приведенный ниже пример. Я не знаю, почему decodeWith не показывается.

{-# 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)
person phadej    schedule 17.07.2015