Функциональное изменение имен ключей в сериализации на aeson с текстовыми ключами - PullRequest
2 голосов
/ 23 апреля 2019

У меня есть объект 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.

Обновление

Когда яиспользуйте 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}’

И когда я пробую голую "каплю"вместо капли 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’

Ответы [ 2 ]

3 голосов
/ 23 апреля 2019

Вы, похоже, используете toLower из Data.Text, который работает с Text, а не с String, поэтому вполне естественно, что он там не подходит.

Вместо этого вы можете использовать toLower из Data.Char и map вместо String:

fieldLabelModifier = map toLower . drop 3
1 голос
/ 23 апреля 2019

Вы составляете две функции T.toLower и drop 3, но типы не совпадают.Действительно, если мы ищем типы, мы видим toLower :: Text -> Text и drop :: Int -> [a] -> [a].String - это список Char с, но Text нет: Text можно рассматривать как упакованный «блок» символов.

Однако мы можем составить функцию типаString -> String, тип поля fieldLabelModifier :: String -> String:

import Data.Char(toLower)

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

Таким образом, мы используем функцию toLower :: Char -> CharData.Char и выполните пинг map, чтобы все символы в строке были сопоставлены.

Обратите внимание, что если вы просто хотите получить FromJson и ToJSON с различными параметрами, вы можетеиспользуйте шаблон 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 будет реализовывать экземпляры FromJSON и ToJSON.

Примечание : мы можем использовать квалифицированный импорт, чтобы было более понятно, какую функцию мы используем, например:
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
        }

Примечание : Что касается интеллектуальныхконструктор, вы можете упростить это выражение до:

makeR3 = R3
...