Преобразование строки JSON в запись в Haskell

#json #parsing #haskell

#json #синтаксический анализ #haskell

Вопрос:

Я изо всех сил пытаюсь понять это (я все еще немного новичок в Haskell), но я нахожу документацию для Text.JSON пакета немного запутанной. В принципе, у меня есть этот тип записи данных: —

 data Tweet = Tweet
    {
        from_user :: String,
        to_user_id :: String,
        profile_image_url :: String,
        created_at :: String,
        id_str :: String,
        source :: String,
        to_user_id_str :: String,
        from_user_id_str :: String,
        from_user_id :: String,
        text :: String,
        metadata :: String
    }
  

и у меня есть несколько твитов в формате JSON, которые соответствуют структуре этого типа. То, с чем я борюсь, заключается в том, как сопоставить вышеупомянутое с тем, что возвращается из следующего кода

 decode tweet :: Result JSValue
  

в указанный выше тип данных. Я понимаю, что я должен создать экземпляр instance JSON Tweet , но я не знаю, что делать дальше.

Любые указатели были бы с благодарностью приняты, спасибо!

Ответ №1:

Я бы рекомендовал вам использовать новый пакет aeson вместо пакета json, поскольку первый работает намного лучше. Вот как можно преобразовать объект JSON в запись Haskell, используя aeson:

 {-# LANGUAGE OverloadedStrings #-}
module Example where

import Control.Applicative
import Control.Monad
import Data.Aeson

data Tweet = Tweet {
    from_user :: String,
    to_user_id :: String,
    profile_image_url :: String,
    created_at :: String,
    id_str :: String,
    source :: String,
    to_user_id_str :: String,
    from_user_id_str :: String,
    from_user_id :: String,
    text :: String,
    metadata :: String
    }

instance FromJSON Tweet where
    parseJSON (Object v) =
        Tweet <$> v .: "from_user"
              <*> v .: "to_user_id"
              <*> v .: "profile_image_url"
              <*> v .: "created_at"
              <*> v .: "id_str"
              <*> v .: "source"
              <*> v .: "to_user_id_str"
              <*> v .: "from_user_id_str"
              <*> v .: "from_user_id"
              <*> v .: "text"
              <*> v .: "metadata"
    -- A non-Object value is of the wrong type, so use mzero to fail.
    parseJSON _          = mzero
  

Затем используйте Data.Aeson.json , чтобы получить анализатор attoparsec, который преобразует a ByteString в Value . Вызов fromJSON на Value , пытающийся преобразовать ее в вашу запись. Обратите внимание, что в этих двух шагах задействованы два разных анализатора: Data.Attoparsec.Parser анализатор для преобразования ByteString в общий JSON Value , а затем Data.Aeson.Types.Parser анализатор для преобразования значения JSON в запись. Обратите внимание, что оба шага могут завершиться неудачей:

  • Первый анализатор может завершиться с ошибкой, если ByteString это недопустимое значение JSON.
  • Второй анализатор может дать сбой, если (допустимое) значение JSON не содержит одного из полей, упомянутых вами в вашей fromJSON реализации.

Пакет aeson предпочитает новый тип Unicode Text (определенный в пакете text) более старому школьному String типу. Этот Text тип имеет гораздо более эффективное представление в памяти, чем String , и обычно работает лучше. Я бы рекомендовал вам изменить Tweet тип, чтобы использовать Text вместо String .

Если вам когда-нибудь понадобится преобразовать между String и Text , используйте функции pack и unpack , определенные в Data.Text . Обратите внимание, что такие преобразования требуют O (n) времени, поэтому избегайте их, насколько это возможно (т. е. всегда используйте Text ).

Ответ №2:

Вам нужно написать метод showJSON and readJSON для вашего типа, который создает ваши значения Haskell из формата JSON. Пакет JSON позаботится о преобразовании необработанной строки в JSValue для вас.

Скорее всего, ваш твит будет JSObject содержать карту строк.

  • Используйте show для просмотра JSObject, чтобы увидеть, как расположены поля.
  • Вы можете выполнить поиск по каждому полю, используя get_field на JSObject .
  • Вы можете использовать fromJSString для получения обычных строк Haskell из JSString .

В целом, вам понадобится что-то вроде,

 {-# LANGUAGE RecordWildCards #-}

import Text.JSON
import Text.JSON.Types

instance JSON Tweet where

    readJSON (JSObject o) = return $ Tweet { .. }
            where from_user         = grab o "from_user"
                  to_user_id        = grab o "to_user_id"
                  profile_image_url = grab o "proile_image_url"
                  created_at        = grab o "created_at"
                  id_str            = grab o "id_str"
                  source            = grab o "source"
                  to_user_id_str    = grab o "to_user_id_str"
                  from_user_id_str  = grab o "from_user_id_str"
                  from_user_id      = grab o "from_user_id"
                  text              = grab o "text"
                  metadata          = grab o "metadata"


grab o s = case get_field o s of
                Nothing            -> error "Invalid field "    show s
                Just (JSString s') -> fromJSString s'
  

Обратите внимание, я использую довольно классное языковое расширение wild cards.

Без примера кодировки JSON я больше ничего не могу посоветовать.


Похожие

Вы можете найти примеры экземпляров для кодировки JSON через instances

  • в исходном коде, для простых типов. Или в других пакетах, которые зависят от json.
  • Экземпляр для сообщений AUR находится здесь в качестве примера (низкого уровня).

Комментарии:

1. Превосходно, спасибо за подробный ответ, который отлично сработал. Могу ли я каким-либо образом поговорить с авторами пакета, чтобы они, возможно, включили подобные примеры (очевидно, отдавая вам должное) в документацию?

Ответ №3:

Импортируйте Data.JSon.Generic и Data.Data, затем добавьте deriving (Данные) к вашему типу записи, а затем попробуйте использовать decodeJSON в твите.

Комментарии:

1. Я пытаюсь это сделать, но не могу найти Data.JSon.Generic . Не могли бы вы указать мне на это?

2. Возможно, он имел в виду данные. Эсон. Общий: hackage.haskell.org/packages/archive/aeson/0.6.0.2/doc/html /…

Ответ №4:

Я поддерживаю ответ @tibbe. Однако я хотел бы добавить, как вы проверяете ввод некоторого значения по умолчанию на случай, если аргумент отсутствует в предоставленном JSON.

В ответе Тиббе вы можете сделать следующее:

 Tweet <$> v .: "from_user"
      <*> v .:? "to_user_id"       .!= "some user here"
      <*> v .: "profile_image_url" .!= "url to image"
      <*> v .: "created_at"
      <*> v .: "id_str"             != 232131
      <*> v .: "source"
  

при разборе JSON будут приниматься параметры dafault.