Обновлено: с простым решением внизу.
Это сбивает с толку, но работает более или менее так, как задумано.Вы можете попытаться отправить его как проблему aeson
, но я подозреваю, что она будет закрыта как "не будет исправлено".
То, что происходит, заключается в том, что общий экземпляр, сгенерированный для FromJSON (Bar a)
, эквивалентен:
instance FromJSON a => FromJSON (Bar a) where
parseJSON = withObject "Bar" $ \v -> Bar
<$> v .:? "payload"
Обратите внимание на использование оператора (.:?)
, сгенерированного из-за поля Maybe a
в Bar
.В структуре со смесью полей Maybe
и не Maybe
будет соответствующая смесь операторов (.:?)
и (.:)
.
Обратите внимание, что этот экземпляр генерируется раз и навсегдаза все возможные a
.Причина его полиморфности в том, что реализация (.:?)
может отправлять методу parseJSON
в словаре FromJSON a
, предоставленном ограничением экземпляра.Также обратите внимание, что единственная причина, по которой мы можем использовать (.:?)
, заключается в том, что во время компиляции известно, что для всех возможных типов a
поле payload
в объекте Bar
имеет тип Maybe a
, поэтому использование *Оператор 1030 * проведет проверку типов.
Теперь рассмотрим экземпляр, сгенерированный для FromJSON (Foo a)
.Это будет эквивалентно:
instance FromJSON a => FromJSON (Foo a) where
parseJSON = withObject "Foo" $ \v -> Foo
<$> v .: "payload"
Это точно аналогично приведенному выше экземпляру Bar a
, за исключением того, что используется оператор (.:)
.Опять же, он имеет единственную реализацию во время компиляции, которая работает для всех возможных a
путем отправки в parseJSON
в словаре FromJSON a
.Нет никакого способа, чтобы этот экземпляр мог использовать оператор (.:?)
, так как общие a
и Maybe t
не могут объединяться, и нет никакого способа, которым он может каким-то образом "проверять" тип a
, будь то во время компиляции иливремя выполнения, чтобы увидеть, является ли это Maybe
, по той же причине, по которой вы не можете написать полную полиморфную функцию с типом a -> a
, отличную от идентификатора.
Следовательно, это Foo a
экземпляр не может сделать поле payload
необязательным!Вместо этого он должен рассматривать payload
как обязательный и - когда используется для анализа Foo (Maybe String)
- отправку в экземпляр FromJSON t => FromJSON (Maybe t)
(что позволяет null
, но в противном случае отправку в экземпляр FromJSON String
).
Теперь, почему это выглядит нормально для ToJSON
?Итак, экземпляры для ToJSON (Foo a)
и ToJSON (Bar a)
генерируют один и тот же вид (мономорфного) представления Value
:
> toJSON (Foo (Nothing :: Maybe String))
Object (fromList [("payload",Null)])
> toJSON (Bar (Nothing :: Maybe String))
Object (fromList [("payload",Null)])
, и удаление пустых полей происходит равномерно, когда это значение кодируется вJSON.
Это приводит к неудачной асимметрии в экземплярах FromJSON
и ToJSON
, но это именно то, что происходит.
И я только что понял, что забыл поделиться простым решением для исправленияЭто.Просто определите два общих экземпляра для Foo
, один перекрывающийся экземпляр для обработки Maybes
, а другой для других типов:
instance {-# OVERLAPPING #-} FromJSON a => FromJSON (Foo (Maybe a))
instance FromJSON a => FromJSON (Foo a)