Заставляем ToJSON использовать Show Instance

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

data SumType = ABC | DEF deriving (Generic, ToJSON)
data MyType = MyType {field1 :: String, field2 :: SumType} deriving (Generic, ToJSON) 

Приведенное выше сгенерирует JSON, который выглядит так: {"field1": "blah", "field2":"ABC"}

На практике MyType является довольно сложным типом, и я хотел бы сохранить ToJSON производное, но хочу настроить только одно поле для использования экземпляра show.

 instance Show SumType where
   show ABC = "abc-blah"
   show DEF = "def-xyz" 

К сожалению, указанный выше экземпляр Show не подхватывается ToJSON (я не знаю, должен ли он это делать). Ручная прокрутка ToJSON для SumType не кажется эффективной, потому что ожидает пары "ключ-значение" (может быть, есть другой способ сделать это?). Другими словами, JSON будет выглядеть так: {"field1": "blah", "field2":{"field3": "ABC"}} - Я просто хочу изменить способ преобразования значения в строку, а не создавать там новый объект.

Любые предложения о том, как изменить строку вывода SumType, не создавая вручную ToJSON для MyType? Таким образом, на выходе получается {"field1": "blah", "field2":"abc-blah"}

Спасибо!


person Ecognium    schedule 01.11.2017    source источник
comment
В чем проблема с созданием ToJSON для SumType? Вы можете использовать определение в Show SumType.   -  person Willem Van Onsem    schedule 01.11.2017
comment
@WillemVanOnsem Я думал, ToJSON принимает только objects. Я не понимал, что могу просто вернуть строку. Если вы вложите свой ответ в ответ, я могу его принять.   -  person Ecognium    schedule 01.11.2017


Ответы (1)


Я не понимаю, в чем проблема с определением экземпляра ToJSON для SumType. Вы можете сделать это с помощью:

import Data.Aeson(ToJSON(toJSON), Value(String))
import Data.Text(pack)

instance ToJSON SumType where
    toJSON = String . pack . show

Или, если вы хотите использовать другие строки для ToJSON, чем Show:

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson(ToJSON(toJSON), Value(String))

instance ToJSON SumType where
    toJSON ABC = String "ABC for JSON"
    toJSON DEF = String "DEF for JSON"

Теперь Haskell будет JSON-кодировать SumType как строку JSON:

Prelude Data.Aeson> encode ABC
"\"ABC for JSON\""
Prelude Data.Aeson> encode DEF
"\"DEF for JSON\""

Вы можете сделать то же самое с FromJSON, чтобы преобразовать строку JSON обратно в объект SumType:

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson(FromJSON(parseJSON), withText)

instance FromJSON SumType where
    parseJSON = withText "SumType" f
        where f "ABC for JSON" = return ABC
              f "DEF for JSON" = return DEF
              f _ = fail "Can not understand what you say!"

Если мы затем проанализируем строку JSON, мы получим:

Prelude Data.Aeson> decode "\"ABC for JSON\"" :: Maybe SumType
Just ABC
Prelude Data.Aeson> decode "\"DEF for JSON\"" :: Maybe SumType
Just DEF
Prelude Data.Aeson> decode "\"other JSON string\"" :: Maybe SumType
Nothing
Prelude Data.Aeson> decode "{}" :: Maybe SumType
Nothing

Поэтому, если мы не декодируем строку JSON, которая соответствует одному из определенных нами шаблонов, синтаксический анализ завершится неудачно. То же самое происходит, если мы предоставляем не строку JSON, а, например, пустой объект JSON.

Дополнительные примечания:

  1. Поскольку SumType здесь имеет два значения, вы также можете использовать логическое значение JSON для кодирования значений.
  2. вы также можете кодировать различные объекты JSON. Например, вы можете использовать строку JSON для ABC и целое число для DEF. Хотя я бы посоветовал не делать этого до тех пор, пока не появятся веские причины (если, например, ABC содержит только строку, а DEF только целое число).
  3. обычно чем сложнее кодирование, тем сложнее будет декодирование.
person Willem Van Onsem    schedule 01.11.2017
comment
Что делает "SumType", переданный withText? Изменить сообщение об ошибке при разборе другим способом? Это было бы хорошо продемонстрировать. - person dfeuer; 07.12.2017