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.
Like,
noPrefixParseJSON :: (Generic a, GFromJSON Zero (Rep a)) => Value -> Parser a
noPrefixParseJSON
= genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
noPrefixToJSON :: (Generic a, GToJSON Zero (Rep a)) => a -> Value
noPrefixToJSON
= genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })
instance FromJSON User where parseJSON = noPrefixParseJSON
instance ToJSON User where toJSON = noPrefixToJSON
instance FromJSON Role where parseJSON = noPrefixParseJSON
instance ToJSON Role where toJSON = noPrefixToJSON
...
Sure this is still kind of repetitive, but I'd say this isn't any cause of worry any more.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With