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

时间:2018-01-27 09:55:31

标签: haskell aeson

Github Gists Rest API回复的JSON包含Haskell's keyword type。但type不能用作记录字段。

因此,它不能用于Aeson's 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

问题: 是否有适当的方法来处理这种冲突?

2 个答案:

答案 0 :(得分:6)

我们可以使用TemplateHaskell来解决这个问题。我们可以使用密钥的特定映射,而不是编写ToJSONFromJON

首先,我们必须为 not 类型的字段构建名称,例如:

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)

现在我们可以使用构建fromJSONtoJSON实例的deriveJSON :: Options -> Name -> Q [Dec]函数。

此处的关键是Options参数:它包含 fieldLabelModifier :: String -> String 字段,可以将字段名称重写为JSON中的键。因此,我们可以生成一个重写它​​的函数。

所以我们首先构造一个函数ownerFieldRename :: String -> String

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

所以这个函数作为一个身份函数,"owner_type"除外,"type"映射在deriveJSON上。

现在我们可以使用自定义选项调用$(deriveJSON defaultOptions {fieldLabelModifier = ownerFieldRename} ''Owner) 函数,例如:

RenameUtils.hs

或完整:

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

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)

type

现在我们获得一个以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}" 为键的JSON对象:

fieldLabelModifier

对于一个简单的MainFile.hs函数,我们不需要编写特定的函数(我们必须在特定的模块中定义),我们也可以在这里使用 lambda表达式:< / p>

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

UserSettings

答案 1 :(得分:2)

Willem的答案可能更合适,并且可能更符合您的要求,但这是另一种方式,允许您定义非冲突数据,而无需为其编写ToJSON和FromJSON实例,定义类型< / p>

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

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

我们现在可以定义以下实例:

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

我使用的导入和语言编译语:

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