2

Aeson を使用して Bitly 応答を解析しようとして頭を悩ませてきました。どの Haskell 型を定義する必要があるか、および Aeson を使用して以下をそれらの型に解析する方法について、誰かが私にヒントを与えることができますか?:

// BITLY EXPAND RESPONSE
{
  "data": {
    "expand": [
      {
        "global_hash": "900913",
        "long_url": "http://google.com/",
        "short_url": "http://bit.ly/ze6poY",
        "user_hash": "ze6poY"
      }
    ]
  },
  "status_code": 200,
  "status_txt": "OK"
}

// BITLY SHORTEN RESPONSE
{
  "data": {
    "global_hash": "900913",
    "hash": "ze6poY",
    "long_url": "http://google.com/",
    "new_hash": 0,
    "url": "http://bit.ly/ze6poY"
  },
  "status_code": 200,
  "status_txt": "OK"
}

これが私がこれまでに試したことです:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}

module BitlyClientResponses where

import           Control.Applicative
import           Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as L (pack)
import qualified Data.HashMap.Strict        as M

data DataStatusCodeStatusTxt =
    DSCST { ddata       :: ResponseData
          , status_code :: Integer
          , status_txt  :: String
          }
    deriving (Eq, Show)

data ResponseData
  = ExpandResponseData { expand :: [Response]
                       }
    deriving (Eq, Show)

data Response = ExpandResponse { long_url    :: String -- URI
                               , global_hash :: String
                               , short_url   :: String -- URI
                               , user_hash   :: String
                               -- , hash        :: [String]
                               -- , error       :: String
                               }
              | J String
              | N String
    deriving (Eq, Show)

instance FromJSON DataStatusCodeStatusTxt where
    parseJSON (Object o) = DSCST <$>
                               o .: "data" <*>
                               o .: "status_code" <*>
                               o .: "status_txt"
    parseJSON x = fail $ "FAIL: DataStatusCodeStatusTxt: " ++ (show x)

instance FromJSON ResponseData where
    parseJSON (Object o) =
        case M.lookup "expand" o of
            -- LOST RIGHT HERE
            Just v  -> return $ ExpandResponseData [J ((show o) ++ " $$$ " ++ (show v))]
            Nothing -> return $ ExpandResponseData [N "N"]
    parseJSON x =  fail $ "FAIL: ResponseData: " ++ (show x)

instance FromJSON Response where
    parseJSON (Object o) = ExpandResponse         <$>
                               o .: "long_url"    <*>
                               o .: "global_hash" <*>
                               o .: "short_url"   <*>
                               o .: "user_hash"
                               -- o .: "hash"        <*>
                               -- o .: "error"       <*>
    parseJSON x =  fail $ "FAIL: Response: " ++ (show x)

parseResponse :: String -> Either String DataStatusCodeStatusTxt
parseResponse x = eitherDecode $ L.pack x

入力時(読みやすくするために手で編集):

"{ \"status_code\": 200,
   \"status_txt\": \"OK\",
   \"data\": { \"expand\": [
                            { \"short_url\": \"http:\\/\\/bit.ly\\/LCJq0b\",
                              \"long_url\": \"http:\\/\\/blog.swisstech.net\\/2012\\/06\\/local-postfix-as-relay-to-amazon-ses.html\",
                              \"user_hash\": \"LCJq0b\",
                              \"global_hash\": \"LCJsVy\" }, ...

私は戻ってきます(手編集も):

Right
  (Right
    (DSCST
      {ddata = ExpandResponseData {expand = [J "fromList [(\"expand\",Array (fromList [Object fromList [(\"long_url\",String \"http://blog.swisstech.net/2012/06/local-postfix-as-relay-to-amazon-ses.html\"),(\"global_hash\",String \"LCJsVy\"),(\"short_url\",String \"http://bit.ly/LCJq0b\"),(\"user_hash\",String \"LCJq0b\")], ...
$$$
Array (fromList [Object fromList [(\"long_url\",String \"http://blog.swisstech.net/2012/06/local-postfix-as-relay-to-amazon-ses.html\"),(\"global_hash\",String \"LCJsVy\"),(\"short_url\",String \"http://bit.ly/LCJq0b\"),(\"user_hash\",String \"LCJq0b\")], ...

コードで、 を探します-- LOST RIGHT HERE。の配列を解析する方法がわかりません"expand"

進歩する方法を見るのは素晴らしいことです。そして、おそらく私は間違った道を進んでおり、誰かが私を正すことができます (たとえば、これまでに定義したデータ型が間違っている可能性があります)。

4

1 に答える 1

4

効果的に使用するコツは、再帰的Aesonに呼び出すことです。parseJSONこれは、演算子を使用すると暗黙的に行われる(.:)ため、通常、次のようなM.lookup表示は悪い兆候です。JSON オブジェクトの JSON 配列で表される (緯度、経度) ペアのパスという簡単な例を示します。

data Path  = Path  { points :: [Point] }
data Point = Point { lat :: Double, lon :: Double }

-- JSON format looks a bit like this
--
-- { "points": [ {"latitude": 86, "longitude": 23} ,
--               {"latitude": 0,  "longitude": 16} ,
--               {"latitude": 43, "longitude": 87} ] }

instance FromJSON Path where
  parseJSON = withObject "path" $ \o -> 
    Path <$> o .: "points"

instance FromJSON Point where
  parseJSON = withObject "point" $ \o ->
    Point <$> o .: "latitude"
          <*> o .: "longitude"

このスニペットから取り除かなければならない 2 つの主要なポイントがあります。まず、渡された toが としてタグ付けされているwithObjectことをすばやく制約するために を使用していることに注意してください。これは、パターン マッチングを使用する場合と大きな違いはありませんが、自動で統一されたエラー メッセージが生成されるため、検討する価値があります。ValueparseJSONObject

次に、さらに重要なことFromJSONとして、各オブジェクトの概要を説明するインスタンスのみを定義していることに注意してください。特に、の体を調べますFromJSON Path

Path <$> o .: "points"

これが言っているのは、という名前のエントリを調べて、この場合はs,のリスト"points"を作成するために必要なタイプとして解析する必要があるということだけです。この使用は、再帰的に定義されたインスタンスに依存します。配列を解析する必要がありますが、幸いにもインスタンスが既に存在しますPathPoint[Point]FromJSONFromJSON

instance FromJSON a => FromJSON [a] where ...

これは、解析可能な JSON 型の JSON 配列として解釈されますa。私たちの場合a ~ Point、そのインスタンスを定義するだけです

instance FromJSON Point where ...

そして、再帰的に依存します

instance FromJSON Double where ...

これは非常に標準的です。


使用できるもう 1 つの重要なトリックは、複数の解析を で隣接させることです(<|>)Responseデータ型を少し単純化して、特定の型として解析するかObject、失敗して、デフォルトとして動的に型指定された単純なものを生成Valueします。まず、各パーサーを個別に記述します。

data Obj = Obj { foo :: String, bar :: String }
         | Dyn Value

okParse :: Value -> Parser Obj
okParse = withObject "obj" (\o -> Obj <$> o .: "foo" <*> o .: "bar")

elseParse :: Value -> Parser Obj
elseParse v = pure (Dyn v)

そして今、実際のFromJSONインスタンスでそれらを組み合わせます

instance FromJSON Obj where
  parseJSON v = okParse v <|> elseParse v

この場合、aesonは最初に を使用しようとしokParse、失敗した場合は にフォールバックしelseParseます。elseParseは単なる値であるためpure、失敗することはなく、「デフォルト」のフォールバックが提供されます。

于 2014-03-19T18:30:37.913 に答える