#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.