5

我的问题似乎与这个密切相关 。

我的代码解析一个 yaml 文件,重新排列对象并编写一个新的 yaml 文件。它工作得很好,但其中有一个特别丑陋的部分。

我必须将我的数据结构声明为这样的FromJson实例ToJson

instance FromJSON Users where
  parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
instance ToJSON Users where
  toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })

问题是我必须在其他 8 个左右的情况下重复此操作:

instance FromJSON Role where
  parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
instance ToJSON Role where
  toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })

...
...

我不知道如何避免这种重复。是否有某种方法可以只声明这两个函数一次(例如在一个新类中)并让所有这些数据类型从中派生?

解决方案(另见 dfeuer 接受的答案):

我个人喜欢这个解决方案。您需要添加

{-# 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

然后你可以像这样声明类型:

data User = User { ... } deriving (Show, Generic)
                         deriving FromJSON via (NP User)
                         deriving ToJSON via (NP User)
4

3 回答 3

5

这就是这个相当新的DerivingVia扩展的用途。

{-# 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

现在,你可以写

deriving via (NP User) instance FromJSON User

或者

data User = ...
  deriving Generic
  deriving (FromJSON, ToJSON) via (NP User)

等等。

这并没有比leftaroundabout的答案节省很多。然而,一旦你添加了 的定义toEncoding,它就开始变得有价值了。

注意:我没有测试过这些。

于 2019-03-21T02:14:52.903 回答
2

喜欢,

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
...

当然这仍然是重复的,但我想说这不再是任何担心的原因。

于 2019-03-20T19:38:22.750 回答
0

如果显式声明所有这些类似的实例证明过于繁重,也许您可​​以使用幻像类型参数化您的数据类型,例如

data User x = User { aa :: Int, bb :: Bool } deriving Generic

data Role x = Role { xx :: Int, dd :: Bool } deriving Generic

然后定义一个“标记”数据类型,如

data Marker

此数据类型将仅用作挂起实例的挂钩,如下所示

{-# language UndecidableInstances #-}
instance (Generic (f Marker), GFromJSON Zero (Rep (f Marker))) => FromJSON (f Marker) where 
    parseJSON = noPrefixParseJSON

值得吗?可能不会,因为数据类型的定义变得更加复杂。另一方面,您可以通过更改标记来更改序列化的各个方面,从而获得一些灵活性。

于 2019-03-20T22:13:03.190 回答