2011-07-26 151 views
13

我試圖用aeson如何用aeson解析嵌套的JSON

{"field":{"name":"..."}} 

or 

{"tag":{"name":"..."}} 

or 

{"line":{"number":"..."}} 

解析以下形式的JSON來構造以下數據類型

data Rule = Line Integer 
      | Field L.ByteString 
      | Tag L.ByteString 

不幸的是,我面臨兩個我找不到解決方案的問題,即:

  1. 如何解析嵌套的JSON?查看(.:)的實現,它使用lookup來提取特定鍵的值。我對做這樣的事情猶豫不決,因爲它似乎過分依賴於aeson如何實現事物的具體細節。我認爲這是一個問題我錯了嗎?

  2. 如何根據JSON中存在的鍵使用正確的數據構造函數?我用< |>的所有努力都讓我無處可去。

我會發布到目前爲止我寫的代碼,但我還沒有達到我有什麼值得發佈的地步。

回答

9

以下情況如何?

{-# LANGUAGE OverloadedStrings #-} 

import Control.Applicative 
import   Data.Aeson 
import   Data.Aeson.Types 
import qualified Data.ByteString  as B 
import qualified Data.ByteString.Lazy as L 
import qualified Data.Map    as M 

data Rule = Line Integer 
      | Field L.ByteString 
      | Tag L.ByteString 
      deriving Show 

instance FromJSON Rule where 
    parseJSON j = do 
    o <- parseJSON j -- takes care of JSON type check 
    case M.toList (o :: Object) of 
     [("field", Object o')] -> Field <$> o' .: "name" 
     [("tag", Object o')] -> Tag <$> o' .: "name" 
     [("line", Object o')] -> Line <$> o' .: "number" 
     _      -> fail "Rule: unexpected format" 
+0

非常感謝,這正是我一直在尋找的!我對前兩行做了一些小修改,使它成爲'parseJSON(Object o)= case M.toList of',並且添加了一個單獨的'parseJSON _ = mzero'。 –

+0

@luke_randall,順便說一句,我沒有故意使用'o < - parseJSON j'而不是'mzero',因爲'mzero'沒有提供任何有關超出單純'「mzero」的問題的有用信息,而'parseJSON'給你錯誤信息,比如''等待映射文本a時,碰到數組',而不是' – hvr

+0

好吧,這很有道理。謝謝你解釋你的推理 - 我想我會恢復它。 –

6

對於這個問題,我創建了一個輔助函數查找一個關鍵:

lookupE :: Value -> Text -> Either String Value 
lookupE (Object obj) key = case H.lookup key obj of 
     Nothing -> Left $ "key " ++ show key ++ " not present" 
     Just v -> Right v 
loopkupE _ _    = Left $ "not an object" 

,並用它的兩個函數嵌套進對象:

(.:*) :: (FromJSON a) => Value -> [Text] -> Parser a 
(.:*) value = parseJSON <=< foldM ((either fail return .) . lookupE) value 

(.:?*) :: (FromJSON a) => Value -> [Text] -> Parser (Maybe a) 
(.:?*) value = either (\_ -> return Nothing) (liftM Just . parseJSON) 
       . foldM lookupE value 
-- Or more simply using Control.Alternative.optional 
-- (.:?*) value keys = optional $ value .:* keys 

只有lookupE依賴於內部表示,所以很容易修改它,如果更改。然後{"tag":{"name":"..."}}被解析爲v .:* ["tag", "name"]。請注意,它也適用於空列表 - v .:* []相當於parseJSON v