在Haskell中,如何解码可能有两种不同类型的JSON值?

时间:2014-03-01 22:48:08

标签: json parsing haskell aeson

我正在尝试解析一些书目数据,更具体地说,为每个项目提取“主题”字段。数据是json,看起来像这样:

{"rows": [

      {"doc":{"sourceResource": {"subject": ["fiction", "horror"]}}},
      {"doc":{"sourceResource": {"subject": "fantasy"}}}
]}

如果每个条目都是Text或[Text],我可以拉出'subject',但我很难理解如何同时容纳这两个条目。这是我目前状态下的程序:

{-# LANGUAGE OverloadedStrings#-}
import Debug.Trace
import Data.Typeable
import Data.Aeson
import Data.Text
import Control.Applicative
import Control.Monad
import qualified Data.ByteString.Lazy as B
import Network.HTTP.Conduit (simpleHttp)
import qualified Data.HashMap.Strict  as HM
import qualified Data.Map as Map

jsonFile :: FilePath
jsonFile = "bib.json"

getJSON :: IO B.ByteString
getJSON = B.readFile jsonFile


data Document = Document { rows :: [Row]}
              deriving (Eq, Show)


data Row = SubjectList [Text]
         | SubjectText Text
         deriving (Eq, Show)


instance FromJSON Document where
  parseJSON (Object o) = do
    rows <- parseJSON =<< (o .: "rows")
    return $ Document rows
  parseJSON _ = mzero


instance FromJSON Row where
  parseJSON (Object o) = do
    item <- parseJSON =<< ((o .: "doc") >>=
                           (.: "sourceResource") >>=
                           (.: "subject"))
    -- return $ SubjectText item
    return $ SubjectList item
  parseJSON _ = mzero

main :: IO ()
main = do
   d <- (decode <$> getJSON) :: IO (Maybe Document)
   print d

任何帮助都将不胜感激。

编辑:

工作FromJSON Row实例:

instance FromJSON Row where
  parseJSON (Object o) =
    (SubjectList <$> (parseJSON =<< val)) <|>
    (SubjectText <$> (parseJSON =<< val))
    where
      val = ((o .: "doc") >>=
             (.: "sourceResource") >>=
             (.: "subject"))
  parseJSON _ = mzero

1 个答案:

答案 0 :(得分:2)

首先,看看

的类型
((o .: "doc") >>=
 (.: "sourceResource") >>=
 (.: "subject")) :: FromJSON b => Parser b

我们可以摆脱任何FromJSON的实例。现在,显然,这可以单独用于Text[Text],但问题是您要获得Text [Text]。幸运的是,处理这个问题应该相当容易。而不是让它进一步解码它,只需从中获取Value。获得Value后,您可以将其解码为Text并将其放入SubjectText

SubjectText <$> parseJSON val :: Parser Row

[Text]并将其放入SubjectList

SubjectList <$> parseJSON val :: Parser Row

但是等等,其中任何一个都可以,并且它们具有相同的输出类型。请注意,ParserAlternative的一个实例,它可以让我们准确地说(“任何一个会做”)。因此,

(SubjectList <$> parseJSON val) <|> (SubjectText <$> parseJSON val) :: Parser Row

钽哒! (实际上,没有必要将它作为Value拉出来;我们可以将长((o .: "doc") >>= (.: "sourceResource") >>= (.: "subject"))链嵌入到每个子表达式中。但这很难看。)