将JSON解析成RoseTree(递归解析)

Parsing JSON into RoseTree (recursive parsing)

我正在为将 String JSON 作为输入和 return RoseTree 结构作为输出的函数而苦苦挣扎。

我的代码如下:

import qualified Data.Aeson as JSON
import qualified Data.Text as T
import qualified Data.Aeson.Types as AT
import Control.Applicative
import qualified Data.ByteString.Char8 as BS
import Data.Attoparsec.ByteString.Char8

data RoseTree a = Empty | RoseTree a [RoseTree a]
                   deriving (Show)

instance (Show a) => JSON.ToJSON (RoseTree a) where
    toJSON (RoseTree n cs) =
        JSON.object [T.pack "value" JSON..= show n
        , T.pack "children" JSON..= JSON.toJSON cs] 

instance (Show a, JSON.FromJSON a) => JSON.FromJSON (RoseTree a) where
    parseJSON (JSON.Object o) =
        RoseTree <$> o JSON..: T.pack "value"
        <*> o JSON..: T.pack "children"

parseRoseTreeFromJSON :: (Show a, JSON.FromJSON a) => String -> (RoseTree a)
parseRoseTreeFromJSON json = 
      let bs = BS.pack json in case parse JSON.json bs of
               (Done rest r) -> case AT.parseMaybe JSON.parseJSON r of
                    (Just x) -> x
                    Nothing -> Empty 
               _ -> Empty

它将 RoseTree 结构转换为 JSON 非常好。比如我运行JSON.encode $ JSON.toJSON $ RoseTree 1 [],它returns"{\"children\":[],\"value\":\"1\"}",运行ningJSON.encode $ JSON.toJSON $ RoseTree 1 [RoseTree 2 []]returns"{\"children\":[{\"children\":[],\"value\":\"2\"}],\"value\":\"1\"}".

但是当我尝试提供 JSON 我已经进入解析器的前一步时,它总是 returns Empty:

*Main> parseRoseTreeFromJSON "{\"children\":[],\"value\":1}"
Empty

*Main> parseRoseTreeFromJSON "{\"children\":[{\"children\":[],\"value\":\"2\"}],\"value\":\"1\"}"
Empty

同样,

*Main> JSON.decode $ BLS.pack "{\"children\":[{\"children\":[],\"value\":\"2\"}],\"value\":\"1\"}"
Nothing

我怀疑我的解析器定义不正确。但我不能说出到底出了什么问题。我使用正确的方法来处理递归解析吗?递归执行此操作的正确方法是什么?

奇怪的 GHCi 打字规则再次来袭!如果您添加类型注释,一切都会按预期工作:

ghci> :set -XOverloadedStrings
ghci> JSON.decode "{\"children\":[],\"value\":1}" :: Maybe (RoseTree Int)
Just (RoseTree 1 [])

在不知道你试图解析什么类型的情况下(它看到 FromJSON a => Maybe a),GHCi 默认为 a ~ ()。您可以通过测试 FromJSON () 实例并注意到它成功来进行尝试!

ghci> JSON.decode "[]"
Just ()

如果这实际上是为了一个项目(而不仅仅是为了娱乐和学习),请在您的代码中添加一些旁注:

  • 我鼓励你查看 OverloadedStrings(你基本上可以从你的代码中删除几乎所有 T.pack 的使用,因为字符串文字将推断它们是否应该具有类型 String,lazy/strict Text, lazy/strict ByteString 等)
  • 您可以使用 DeriveGenericDeriveAnyClass 获得几乎免费的 JSON 解析(尽管我承认行为会 略有 不同从你目前拥有的)

根据这些建议,重写您的代码:

{-# LANGUAGE OverloadedStrings, DeriveGeneric, DeriveAnyClass #-}

import qualified Data.Aeson as JSON
import qualified Data.ByteString.Lazy.Char8 as BS
import GHC.Generics (Generic)
import Data.Maybe (fromMaybe)

data RoseTree a = RoseTree { value :: a, children :: [RoseTree a] }
                    deriving (Show, Generic, JSON.FromJSON, JSON.ToJSON)

请注意 Data.Aeson 中的 decode 的作用(几乎)与 parseRoseTreeFromJSON 的作用相同...

ghci> :set -XOverloadedStrings
ghci> JSON.encode (RoseTree 1 [])
"{\"children\":[],\"value\":1}"
ghci> JSON.encode (RoseTree 1 [RoseTree 2 []])
"{\"children\":[{\"children\":[],\"value\":2}],\"value\":1}"
ghci> JSON.decode "{\"children\":[],\"value\":1}" :: Maybe (RoseTree Int)
Just (RoseTree {value = 1, children = []})
ghci> JSON.decode "{\"children\":[{\"children\":[],\"value\":2}],\"value\":1}" :: Maybe (RoseTree Int)
Just (RoseTree {value = 1, children = [RoseTree {value = 2, children = []}]})