My question seems to be closely related to this one.
My code parses a yaml file, rearanges the objects and writes a new yaml file. It works perfectly well, but there is a particularly ugly part in it.
I have to declare my data structures as instances of FromJson
and ToJson
like this:
instance FromJSON Users where
parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
instance ToJSON Users where
toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })
The problem is that I have to repeat this for 8 or so other cases:
instance FromJSON Role where
parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
instance ToJSON Role where
toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })
...
...
I could not figure out how to avoid this repetition. Is there some method to declare the two functions just once (for example in a new class) and let all these data types derive from it?
Solution (see also accepted answer by dfeuer):
I personally like this solution. You'll need to add
{-# language DerivingVia #-}
{-# language UndecidableInstances #-}
newtype NP a = NP {unNP::a}
instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (NP a) where
parseJSON = fmap NP . genericParseJSON
(defaultOptions { fieldLabelModifier = body_noprefix })
instance (Generic a, GToJSON Zero (Rep a)) => ToJSON (NP a) where
toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix }) . unNP
Then you can declare the types like this:
data User = User { ... } deriving (Show, Generic)
deriving FromJSON via (NP User)
deriving ToJSON via (NP User)
This is what the fairly new DerivingVia
extension is for, among other things.
{-# language DerivingVia #-}
newtype NP a = NP {unNP::a}
instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (NP a) where
parseJSON = fmap NP . genericParseJSON
(defaultOptions { fieldLabelModifier = body_noprefix })
instance (Generic a, GToJSON Zero (Rep a)) => ToJSON (NP a) where
toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix }) . unNP
Now, you can write
deriving via (NP User) instance FromJSON User
Or
data User = ...
deriving Generic
deriving (FromJSON, ToJSON) via (NP User)
and so on.
This doesn't save a lot over leftaroundabout's answer as it is. However, once you add a definition of toEncoding
, it starts to look worthwhile.
Caution: I have tested none of this.