aeson
aeson copied to clipboard
Support for datatypes with no constructors in TH and generics
This PR adds support for datatypes with no constructors in TH generators (mkToJSON, mkToEncoding and mkParseJSON) and generic instances (GToJSON', GFromJSON').
For example, this PR enables this code:
data NoConstructors
deriving (Generic)
instance ToJSON NoConstructors
instance FromJSON NoConstructors
data MyVoid
deriveJSON defaultOptions ''MyVoid
On the current master, both variants fail: the generics are missing instances of GToJSON' Encoding Zero V1 and GFromJSON' Zero V1, and TH makes an explicit call to error.
The actual behavior that is added:
toJSON x = seq x (error "case: V1")toEncoding x = seq x (error "case: V1")parseJSON _ = fail "Attempted to parse empty type"
The main use case is to allow substituting such types into polymorphic sums, where some variants are not used for a particular instantiation, and we need to serialize/deserialize the sum; though, I think it's a natural generalization by itself. As an example of such use case:
class
( ToJSON (ResultType lang)
, ToJSON (FailureType lang)
, FromJSON (ResultType lang)
, FromJSON (FailureType lang)
) =>
RecordableResult (lang :: k)
where
data ResultType lang
data FailureType lang
data RequestOutcome lang
= OutcomeSuccess (ResultType lang)
| OutcomeFailure (FailureType lang)
deriving (Generic)
instance (RecordableResult lang) => ToJSON (RequestOutcome lang)
instance (RecordableResult lang) => FromJSON (RequestOutcome lang)
instance RecordableResult MonadDatabase where
data ResultType MonadDatabase = DatabaseResult Aeson.Value
deriving (Generic, ToJSON, FromJSON)
data FailureType MonadDatabase = DatabaseError DatabaseException
deriving (Generic, ToJSON, FromJSON)
instance (ToJSON r, FromJSON r) => RecordableResult (MonadReader r) where
data ResultType (MonadReader r) = ReaderResult r
deriving (Generic, ToJSON, FromJSON)
data FailureType (MonadReader r) -- MonadReader never fails
deriving (Generic, ToJSON, FromJSON)
-- but we still need ToJSON/FromJSON for the surrounding machinery to work
The tests fail to compile on GHC <= 8.2.2, because this declaration:
https://github.com/haskell/aeson/blob/3de29616ba0d468ef520c1b3621b390e6a10dfe7/tests/Types.hs#L51-L52
uses EmptyDataDeriving, which is available only since GHC 8.4.
This test only asserts the successful compilation of the TH and Generic implementations, and doesn't make any checks for runtime behavior.
We can use #if __GLASGOW_HASKELL__ >= 804 to change this test for older GHCs, though I'm not sure how - should we replace the derived instances with manual ones (and how we'd do that for Typeable?), or it's fine to entirely disable this test?
Typeable is always derived even if deriving Typeable is not there. deriving Typeable is just allowed for backwards compatibility. Do you actually need Show, Eq and Data? What happens if you just remove the deriving clause for NoConstructors?
None of the instances were actually used, so I just removed them.
Merged squashed in https://github.com/haskell/aeson/pull/971 (with some additions). Thanks!