使用文本键在功能上将序列化中的键名更改为 aeson

Functionally changing key names in serialization to aeson with Text keys

我有一个 json 对象和一个手动创建的 ToJSON 实例。我想用一个不需要我显式枚举键名的函数来替换它。

我正在使用 "rec*" 作为我想要去除的前缀,并且我的字段开始时是文本而不是字符串。

从最少的数据开始:

data R3 = R3 { recCode :: Code 
             , recValue :: Value} deriving (Show, Generic)

以及智能构造函数:

makeR3 rawcode rawval = R3 code value where
                                     code = rawcode
                                     value = rawval

这个实现工作正常:

instance ToJSON R3 where
   toJSON (R3 recCode recValue) = object [ "code" .= recCode, "value" .= recValue]

但是正如您所想象的那样,手动输入从 "code" 到 "recCode" 的每个键名并不是我想做的事情。

tmp_r3 = makeR3 "TD" "100.42"
as_json = encode tmp_r3

main = do
    let out = encodeToLazyText tmp_r3
    I.putStrLn out
    I.writeFile "./so.json" out
    return ()

输出正确:

{"value":100.42,"code":"TD"}
-- not recValue and recCode, correct!

但是,当我尝试此功能时,它无法像以前那样自动将文本转换为字符串。

instance ToJSON R3 where
  toJSON = genericToJSON defaultOptions {
             fieldLabelModifier = T.toLower . IHaskellPrelude.drop 3 }

输出:

<interactive>:8:35: error:
    • Couldn't match type ‘Text’ with ‘String’
      Expected type: String -> String
        Actual type: String -> Text
    • In the ‘fieldLabelModifier’ field of a record
      In the first argument of ‘genericToJSON’, namely ‘defaultOptions {fieldLabelModifier = toLower . IHaskellPrelude.drop 3}’
      In the expression: genericToJSON defaultOptions {fieldLabelModifier = toLower . IHaskellPrelude.drop 3}
<interactive>:8:47: error:
    • Couldn't match type ‘String’ with ‘Text’
      Expected type: String -> Text
        Actual type: String -> String
    • In the second argument of ‘(.)’, namely ‘IHaskellPrelude.drop 3’
      In the ‘fieldLabelModifier’ field of a record
      In the first argument of ‘genericToJSON’, namely ‘defaultOptions {fieldLabelModifier = toLower . IHaskellPrelude.drop 3}’

错误本身很明显,Text 不起作用,但我应该更改什么以在 json 输出中功能性地从键名中删除我的前缀 并正确地将文本转换为字符串?

我也有点困惑,我没有改变我的输入,在这两种情况下都是文本类型,但第一个实现可以使用它,而第二个则不行。

我正在使用 ihaskell jupyter notebook。

更新

当我使用以下答案中推荐的 Data.Char 时:

import Data.Char(toLower)

在:

instance ToJSON R3 where
  toJSON = genericToJSON defaultOptions {
             fieldLabelModifier = Data.Char.toLower . IHaskellPrelude.drop 3 }

我得到:

<interactive>:8:35: error:
    • Couldn't match type ‘Char’ with ‘String’
      Expected type: String -> String
        Actual type: String -> Char
    • In the ‘fieldLabelModifier’ field of a record
      In the first argument of ‘genericToJSON’, namely ‘defaultOptions {fieldLabelModifier = Data.Char.toLower . IHaskellPrelude.drop 3}’
      In the expression: genericToJSON defaultOptions {fieldLabelModifier = Data.Char.toLower . IHaskellPrelude.drop 3}
<interactive>:8:55: error:
    • Couldn't match type ‘String’ with ‘Char’
      Expected type: String -> Char
        Actual type: String -> String
    • In the second argument of ‘(.)’, namely ‘IHaskellPrelude.drop 3’
      In the ‘fieldLabelModifier’ field of a record
      In the first argument of ‘genericToJSON’, namely ‘defaultOptions {fieldLabelModifier = Data.Char.toLower . IHaskellPrelude.drop 3}’

当我尝试裸 "drop" 而不是 IHaskellPrelude 下降时,我得到:

instance ToJSON R3 where
  toJSON = genericToJSON defaultOptions {
             fieldLabelModifier = Data.Char.toLower . drop 3 }

<interactive>:8:55: error:
    Ambiguous occurrence ‘drop’
    It could refer to either ‘BS.drop’, imported from ‘Data.ByteString’
                          or ‘IHaskellPrelude.drop’, imported from ‘Prelude’ (and originally defined in ‘GHC.List’)
                          or ‘T.drop’, imported from ‘Data.Text’

你组合了两个函数T.toLowerdrop 3,但是类型不匹配。事实上,如果我们查找类型,我们会看到 toLower :: Text -> Text and drop :: Int -> [a] -> [a]StringChar 的列表,但 Text 不是:Text 可以看作是字符的压缩 "block"。

但是我们可以组合一个String -> String类型的函数,字段的类型fieldLabelModifier :: String -> String:

import Data.Char(toLower)

instance ToJSON R3 where
    toJSON = genericToJSON defaultOptions {
            fieldLabelModifier = <b>map toLower</b> . drop 3
        }

因此我们使用 Data.Char 模块的 toLower :: Char -> Char 函数,并执行 mapping,以便映射字符串中的所有字符。

请注意,如果您只想导出具有不同选项的 FromJsonToJSON,您可以使用 模板 Haskell,喜欢:

{-# LANGUAGE DeriveGeneric, TemplateHaskell #-}

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

data Test = Test { attribute :: String } deriving Show

$(deriveJSON defaultOptions {fieldLabelModifier = map toUpper . drop 3} ''Test)

在这种情况下,模板 Haskell 部分将实现 FromJSONToJSON 实例。

Note: We can use qualified imports in order to make it more clear what function we use, for example:
import <b>qualified</b> Data.List as <b>L</b>
import <b>qualified</b> Data.Char as <b>C</b>

instance ToJSON R3 where
    toJSON = genericToJSON defaultOptions {
            fieldLabelModifier = map <b>C.</b>toLower . <b>L.</b>drop 3
        }

Note: As for the smart constructor, you can simplify this expression to:

makeR3 = R3

您似乎在使用 Data.Text 中的 toLower,它适用于 Text,而不适用于 String,所以很自然地,它不适合那里。

相反,您可以在 String 上使用 toLower from Data.Charmap

fieldLabelModifier = map toLower . drop 3