简体   繁体   English

如何处理记录字段中的Haskell关键字?

[英]How to deal with Haskell's keywords in record fields?

JSON in response of Github Gists Rest API contains Haskell's keyword type . 响应Github Gists Rest API的 JSON包含Haskell的关键字 type But type couldn't be used as a record field. 但是type不能用作记录字段。

Thus it couldn't be used in implementation of Aeson's Generic FromJSON/ToJSON instances. 因此,它不能用于实现Aeson的Generic FromJSON / ToJSON实例。

import Data.Text (Text)

import GHC.Generics (Generic)

type URL = Text

data OwnerType = User deriving (Show)

data Owner = Owner {
      id :: Int,
      gravatar_id :: Text,
      login :: Text,
      avatar_url :: Text,
      events_url :: URL,
      followers_url :: URL,
      following_url :: URL,
      gists_url :: URL,
      html_url :: URL,
      organizations_url :: URL,
      received_events_url :: URL,
      repos_url :: URL,
      starred_url :: URL,
      subscriptions_url :: URL,
      url :: URL,
      -- type :: Text,
      site_admin :: Bool
  } deriving (Generic, Show)

instance ToJSON Owner
instance FromJSON Owner

Question : Is there a proper approach to deal with such kind of conflicts? 问题 :是否有适当的方法来处理此类冲突?

We can solve this by using TemplateHaskell . 我们可以使用TemplateHaskell解决此问题。 Instead of writing ToJSON and FromJON , we can use a specific mapping of the keys. 代替编写ToJSONFromJON ,我们可以使用键的特定映射。

First of all, we have to construct a name for the field that is not type, for instance: 首先,我们必须为非类型的字段构造一个名称,例如:

data Owner = Owner {
      id :: Int,
      gravatar_id :: Text,
      login :: Text,
      avatar_url :: Text,
      events_url :: URL,
      followers_url :: URL,
      following_url :: URL,
      gists_url :: URL,
      html_url :: URL,
      organizations_url :: URL,
      received_events_url :: URL,
      repos_url :: URL,
      starred_url :: URL,
      subscriptions_url :: URL,
      url :: URL,
      owner_type :: Text,
      site_admin :: Bool
  } deriving (Generic, Show)

Now we can use the deriveJSON :: Options -> Name -> Q [Dec] function that will construct a fromJSON and toJSON instance. 现在我们可以使用deriveJSON :: Options -> Name -> Q [Dec]函数,该函数将构造fromJSONtoJSON实例。

The key here is the Options parameter: it contains a fieldLabelModifier :: String -> String field that can rewrite the names of the fields to the keys in JSON. 此处的键是Options参数:它包含fieldLabelModifier :: String -> String字段,可以将字段名称重写为JSON中的键。 We can thus here generate a function that will rewrite it. 因此,我们可以在此处生成一个将其重写的函数。

So we first construct a function ownerFieldRename :: String -> String : 因此,我们首先构造一个函数ownerFieldRename :: String -> String

ownerFieldRename :: String -> String
ownerFieldRename "owner_type" = "type"
ownerFieldRename name = name

So this function acts as an identity function, except for "owner_type" , which is mapped on "type" . 因此,除了映射到"type"上的"type" "owner_type"之外,该函数还用作标识函数。

So now we can call the deriveJSON function with custom options like: 因此,现在我们可以使用诸如以下的自定义选项来调用deriveJSON函数:

$(deriveJSON defaultOptions {fieldLabelModifier = ownerFieldRename} ''Owner)

Or in full: 或全部:

RenameUtils.hs : RenameUtils.hs

module RenameUtils where ownerFieldRename :: String -> String ownerFieldRename "owner_type" = "type" ownerFieldRename name = name

MainFile.hs : MainFile.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson.TH(deriveJSON, defaultOptions, Options(fieldLabelModifier))
import RenameUtils(ownerFieldRename)

import Data.Text (Text)

type URL = Text

data Owner = Owner {
      id :: Int,
      gravatar_id :: Text,
      login :: Text,
      avatar_url :: Text,
      events_url :: URL,
      followers_url :: URL,
      following_url :: URL,
      gists_url :: URL,
      html_url :: URL,
      organizations_url :: URL,
      received_events_url :: URL,
      repos_url :: URL,
      starred_url :: URL,
      subscriptions_url :: URL,
      url :: URL,
      owner_type :: Text,
      site_admin :: Bool
  } deriving (Show)

$(deriveJSON defaultOptions {fieldLabelModifier = ownerFieldRename} ''Owner)

Now we obtain a JSON object with type as key: 现在,我们获得一个type为键的JSON对象:

Prelude Main Data.Aeson> encode (Owner 1 "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" True)
"{\"id\":1,\"gravatar_id\":\"\",\"login\":\"\",\"avatar_url\":\"\",\"events_url\":\"\",\"followers_url\":\"\",\"following_url\":\"\",\"gists_url\":\"\",\"html_url\":\"\",\"organizations_url\":\"\",\"received_events_url\":\"\",\"repos_url\":\"\",\"starred_url\":\"\",\"subscriptions_url\":\"\",\"url\":\"\",\"type\":\"\",\"site_admin\":true}"

For a simple fieldLabelModifier function we do not need to write a specific function (that we have to define in a specific module), we can also use an lambda expression here: 对于简单的fieldLabelModifier函数,我们不需要编写特定的函数(必须在特定的模块中定义),我们还可以在此处使用lambda表达式

MainFile.hs : MainFile.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson.TH(deriveJSON, defaultOptions, Options(fieldLabelModifier))
import Data.Text (Text)

type URL = Text

data Owner = Owner {
      id :: Int,
      gravatar_id :: Text,
      login :: Text,
      avatar_url :: Text,
      events_url :: URL,
      followers_url :: URL,
      following_url :: URL,
      gists_url :: URL,
      html_url :: URL,
      organizations_url :: URL,
      received_events_url :: URL,
      repos_url :: URL,
      starred_url :: URL,
      subscriptions_url :: URL,
      url :: URL,
      owner_type :: Text,
      site_admin :: Bool
  } deriving (Show)

$(deriveJSON defaultOptions {fieldLabelModifier = \x -> if x == "owner_type" then "type" else x} ''Owner)

Willem's answer may be the more appropriate one, and may fit what you want better, but here's another way, allowing you to define your non-conflicting data without having to write the ToJSON and FromJSON instances for it, defining the types Willem的答案可能更合适,并且可能更适合您想要的答案,但是这是另一种方式,允许您定义无冲突的数据,而不必为其编写ToJSON和FromJSON实例,从而定义类型

data OwnerData = OwnerData {
    oid :: Int
    -- ... other data with non-conflicting names
  } deriving (Show, Generic)

and

data Owner = Owner {
  owner_data :: OwnerData,
  user_type :: Text
} deriving (Show)

We can now define the following instances: 现在,我们可以定义以下实例:

-- nothing special for OwnerData: 
instance ToJSON OwnerData
instance FromJSON OwnerData

-- a little helper function to extract the hashmap(Object) from a value
toObject :: ToJSON a => a -> Object
toObject a = case toJSON a of
  Object o -> o
  _        -> error "toObject: value isn't an Object"

-- the instances for Owner
instance ToJSON Owner where
  toJSON (Owner {owner_data = ownerData, user_type = userType}) = 
    Object $ 
    toObject ownerData <> HML.fromList ["type" .= userType]

  toEncoding (Owner {owner_data = ownerData, user_type = userType}) = 
    pairs . foldMap (uncurry (.=)) . HML.toList $ 
    toObject ownerData <> HML.fromList ["type" .= userType]

instance FromJSON Owner where
  parseJSON = withObject "Owner" $ \v -> do
    ownerData <- parseJSON (Object v)
    userType <- v .: "type"
    return Owner { owner_data = ownerData, user_type = userType }

The imports and language pragmas I used: 我使用的导入和语言编译指示:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson
import Data.Text (Text)
import Data.Monoid ((<>))
import GHC.Generics (Generic)
import qualified Data.HashMap.Lazy as HML (fromList, toList)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM