Haskell - Aeson: при попытке декодировать JSON URL Req получаю «ничего» - PullRequest
0 голосов
/ 11 октября 2018

Я относительно новичок в haskell, и сейчас я пытаюсь глубже понять и пытаться привыкнуть к различным популярным библиотекам.

Сейчас я пробую "эзон".

Что я хочу сделать - это проанализировать MSFT-запрос на цитату из https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo

Вот как это выглядит

{
    "Global Quote": {
        "01. symbol": "MSFT",
        "02. open": "105.3500",
        "03. high": "108.2400",
        "04. low": "105.2700",
        "05. price": "107.6000",
        "06. volume": "23308066",
        "07. latest trading day": "2018-10-11",
        "08. previous close": "106.1600",
        "09. change": "1.4400",
        "10. change percent": "1.3564%"
    }
}

Это то, что я получил до сих пор

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}

import           Data.Aeson
import qualified Data.ByteString.Lazy as B
import           GHC.Exts
import           GHC.Generics
import           Network.HTTP
import           Network.URI

jsonURL :: String
jsonURL = "http://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo"

getRequest_ :: HStream ty => String -> Request ty
getRequest_ s = let Just u = parseURI s in defaultGETRequest_ u

jsonReq = getRequest_ jsonURL

data Quote = Quote {quote         :: String,
                    symbol        :: String,
                    open          :: Float,
                    high          :: Float,
                    low           :: Float,
                    price         :: Float,
                    volume        :: Float,
                    ltd           :: String,
                    previousClose :: Float,
                    change        :: Float,
                    changePerct   :: Float
                   } deriving (Show, Generic)

instance FromJSON Quote
instance ToJSON Quote


main :: IO ()
main = do
  d <- simpleHTTP jsonReq
  body <- getResponseBody d
  print (decode body :: Maybe Quote)

Что я делаю не так?

Редактировать: Исправлена ​​версия в ответах.

Ответы [ 2 ]

0 голосов
/ 15 октября 2018

@ Raveline с ответом выше указал мне правильное направление.Я смог решить все эти проблемы, вот конечный продукт!

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

module Test where

import           Data.Aeson
import qualified Data.ByteString.Lazy as B
import           GHC.Exts
import           GHC.Generics
import           Network.HTTP.Conduit (simpleHttp)

jsonURL :: String
jsonURL = "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo"

getJSON :: IO B.ByteString
getJSON = simpleHttp jsonURL

data Quote = Quote {
                   symbol        :: String,
                   open          :: String,
                   high          :: String,
                   low           :: String,
                   price         :: String,
                   volume        :: String,
                   ltd           :: String,
                   previousClose :: String,
                   change        :: String,
                   changePercent :: String
                   } deriving (Show, Generic)


instance FromJSON Quote where
  parseJSON = withObject "Global Quote" $
    \o -> do
      globalQuote   <- o           .: "Global Quote"
      symbol        <- globalQuote .: "01. symbol"
      open          <- globalQuote .: "02. open"
      high          <- globalQuote .: "03. high"
      low           <- globalQuote .: "04. low"
      price         <- globalQuote .: "05. price"
      volume        <- globalQuote .: "06. volume"
      ltd           <- globalQuote .: "07. latest trading day"
      previousClose <- globalQuote .: "08. previous close"
      change        <- globalQuote .: "09. change"
      changePercent <- globalQuote .: "10. change percent"
      return Quote {..}


main :: IO ()
main = do
  d <- (eitherDecode <$> getJSON) :: IO (Either String Quote)
  case d of
    Left e   -> print e
    Right qt -> print (read (price qt) :: Float)
0 голосов
/ 11 октября 2018

Во-первых: Aeson не самая простая библиотека для новичка .Конечно, есть и более сложные, но они предполагают, что вы уже достаточно много говорите о языке.Вы не выбрали «простейшую задачу» для начала.Я знаю, что это может быть удивительно, и вы можете подумать, что синтаксический анализ JSON должен быть простым, но синтаксический анализ JSON с надежными гарантиями типов на самом деле не так прост.

Но вот что я могу вам сказать, чтобы помочь вам немного:

  • Сначала используйте eitherDecode вместо decode: вместо сообщения Nothing вы получите сообщение об ошибке, которое немного вам поможет.

  • Получение с помощью Generic аккуратно и очень часто экономит время, но это также не волшебство.Имя ключа объекта и имя ваших полей типов данных должны точно совпадать.К сожалению, это не тот случай, и из-за синтаксиса haskell вы не можете назвать свои поля как ключи объекта.Лучшее решение - внедрить FromJSON вручную (см. Рекомендованную ссылку ниже).Хороший способ увидеть «что ожидается» от универсального FromJSON - это также получить ToJSON, создать фиктивную Quote и посмотреть результат encode.

  • Ваше первое поле(quote) - это не ключ самого объекта, а имя этого объекта.Таким образом, у вас есть динамические ключи (здесь есть глобальная цитата).Еще раз, это обычно тот случай, когда вы хотите написать экземпляр FromJSON вручную.

Я рекомендую вам прочитать этот знаменитый учебник , написанный Артемом Казаком для Aeson.Это очень поможет вам и, вероятно, будет лучшим советом, который я могу дать.

Для вашего ручного примера, предположим, что это был точно документ, который вы хотите проанализировать, и у вас была только "Глобальная цитата"«чтобы иметь дело с этим, это выглядело бы более или менее так:

instance ToJSON Quote where
  parseJSON = withObject "Document" $
    \d -> do
        glob <- d .: "Global Quote"
        withObject "Quote" v (\gq ->
          Quote <$> gq .: "01. symbol"
                <*> pure "Global Quote"
                <*> gq .: "02. open"
                <*> gq .: "03. high"
          -- ... and so on
         ) v

(Это не самый красивый и не лучший способ написать это, но это должен быть один из возможных способов).

Также обратите внимание, что, как писал проницательный комментатор, типы ваших полей не всегда совпадают с типом вашего примера JSON-документа.«volume» - это Int (целое число, ограниченное байтами), потенциально Integer («математическое» целое число, без ограничений), но не Float.Ваш "ltd" может быть проанализирован на строку - но это, вероятно, должна быть дата (Day из Data.Time будет первым выбором - у него уже есть экземпляр FromJSON, поэтому есть вероятность, что он должен быть разобран как есть).Процент изменений, скорее всего, не разбирается как Float, поэтому вам нужно написать специальный анализатор для этого типа (и решить, как вы хотите его сохранить - Ratio - это потенциальное решение).

...