使用 Aeson/JSON 在派生的 Aeson FromJSON 实例中处理“id”

Handling `id` in derived Aeson FromJSON instances with Aeson/JSON

如果我有 JSON 并且我尝试使用 Generics 自动派生 FromJSON 个实例,我 运行 遇到 id 存在的问题 more比 JSON 中的一个位置。

有没有办法让我只覆盖 id 部分,或者我是否必须编写整个实例才能更改这些特定条目? JSON 实际上有更多的字段,但我在这个例子中省略了大部分。所以写出整个 FromJSON 实例实际上是相当乏味的。

JSON:

{
  "response": [
    {
      "id": 1,
      "brandId": 1,
      "productTypeId": 1,
      "identity": {
        "sku": "x",
        "barcode": "Ax"
      },
      "stock": {
        "stockTracked": false,
        "weight": {
          "magnitude": 0
        },
        "dimensions": {
          "length": 0,
          "height": 0,
          "width": 0,
          "volume": 0
        }
      },
      "financialDetails": {
        "taxable": false,
        "taxCode": {
          "id": 1,
          "code": "x"
        }
      },
... etc
  ]
}

到目前为止代码:

data Response = Response
    { response :: [Body]
    } deriving (Show,Generic)

data Body = Body
    { id                    :: Int
    , brandId               :: Int
    , productTypeId         :: Int
    , identity              :: Identity
    , productGroupId        :: Int
    , stock                 :: Stock
    , financialDetails      :: FinancialDetails
    } deriving (Show,Generic)                  

data Identity = Identity
    { sku       :: String
    , ean       :: String
    , barcode   :: String
    } deriving (Show,Generic)                  

data Stock = Stock
    { stockTracked  :: Bool
    , weight        :: Weight
    , dimensions    :: Dimensions
    } deriving (Show,Generic)                  

data Weight = Weight
    { magnitude  :: Int
    } deriving (Show,Generic)                  

data Dimensions = Dimensions
    { length :: Int
    , height :: Int
    , width  :: Int
    , volume :: Int
    } deriving (Show,Generic)                  

data FinancialDetails = FinancialDetails
     { taxable :: Bool
     , taxCode :: TaxCode
     } deriving (Show,Generic)                  

data TaxCode = TaxCode
     { id      :: Int
     , code    :: String 
     } deriving (Show,Generic)                  


instance FromJSON Response
instance FromJSON Body
instance FromJSON Identity
instance FromJSON Stock
instance FromJSON Weight
instance FromJSON Dimensions
instance FromJSON FinancialDetails

这给出了错误:

[1 of 1] Compiling Main             ( reponse.hs, interpreted )

response.hs:73:8:
    Multiple declarations of `id'
    Declared at: response.hs:19:7
                 response.hs:73:8
Failed, modules loaded: none.

理想情况下,我想将第一个 id 更改为 body_id,将第二个更改为 taxCode_id,而不必写出整个实例。

问题不在于 GHC 无法提供泛型,而是在 Haskell 中记录标签也是访问函数,因此如果您尝试对两个不同的标签使用相同的标签,就会出现名称冲突记录。

如果您使用 Data.Aeson.TH 中的函数,您可以使用 fieldLabelModifier 选项,例如。从标签中删除前缀。

data Identity = Identity
{ identitysku       :: String
, identityean       :: String
, identitybarcode   :: String
} deriving (Show)

$(deriveJSON defaultOptions{fieldLabelModifier = drop (length "identity")} ''Identity)

此代码需要 {-# LANGUAGE TemplateHaskell #-} 编译指示。

派生 FromJSON 实例时,您可以将选项传递给 genericParseJSON 函数。通常是

data Foo = {- ... -} deriving (Show, Generic)

instance FromJSON Foo where
    parseJSON = genericParseJSON defaultOptions
    -- defaultOptions :: Options

虽然您可以将 defaultOptions 替换为您手动构建的 OptionOption 类型有一个字段 fieldLabelModifier 可以预处理数据类型的字段名称。您可以将数据类型定义为

data Body = Body
  { body_id :: Int
  ...

并编写一个辅助函数,将 "body_id" 映射到 "id" 并且其他任何不变:

body_noprefix "body_id" = "id"
body_noprefix s = s

然后定义实例为

instance FromJSON Body where
  parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })

其他人提供了修改生成的实例的好方法,这有时是最好的做法。但是,这不是您唯一的选择。您还可以选择在单独的模块中定义一种或多种类型,在这些模块中生成实例,然后导入限定的模块或以其他方式使用限定名称来引用重叠的字段名称。