使用"随机"解析嵌套的JSON;使用aeson的整数键

时间:2014-03-27 23:04:20

标签: json haskell graph monads aeson

我使用aeson库为我的自定义Graph类型生成和解析json文件。这是类型定义。

type Id = Int
type Edge = (Id, Id)
type Scenario = [Id]
data Point = Point Int Int
data Vertex = Vertex {-# UNPACK #-}!Id {-# UNPACK #-}!Point deriving (Show)
data Graph = Graph Id [Vertex] Scenario deriving (Show)

实际上我正在使用欧拉和半欧拉图,其所有顶点都在2D空间中有位置。简而言之,Graph使用Data.Graph,但这与我的问题无关。每个图表都有自己的ID,可以快速识别它。

以下是json文件的示例,其中包含有关我的图表的信息:

{
    "id": 1,
    "vertices": {
        "3": {
            "y": 12,
            "x": 0
        },
        "2": {
            "y": 16,
            "x": 24
        },
        "1": {
            "y": 12,
            "x": 10
        }
    },
    "scenario": [
        1,
        2,
        3,
        1
    ]
}

所以,这是toJSON函数的实现:

import qualified Data.Text                     as T

instance ToJSON Graph where
  toJSON (Graph id v s) = object [ "vertices" .= object (map vertexToPair v)
                                 , "scenario" .= s
                                 , "id" .= id
                                 ]
    where
      vertexToPair :: Vertex -> (T.Text, Value)
      vertexToPair (Vertex id (Point x y)) =
        (T.pack $ show id) .= object [ "x" .= x, "y" .= y]

但实际上我从json-file解析回来时遇到了问题。主要的问题是,我们不知道有多少顶点具有特定的Graph,因此它不能被硬编码。这是我第一次尝试编写parseJSON函数:

instance FromJSON Graph where
  parseJSON (Object v) = do
    i <- parseJSON =<< v .: "id"
    vs <- parseJSON =<< v .: "vertices"
    sc <- parseJSON =<< v .: "scenario"
    maybeReturn ((buildGraph i sc) <$> (parseVertices vs 1))
      where
        parseVertices :: Value -> Int -> Maybe [Vertex]
        -- parseVertices (Object o) i = ???
        parseVertices _ _ = Just []

        buildGraph :: Int -> Scenario -> [Vertex] -> Graph
        buildGraph i sc vertices = Graph i vertices sc

        maybeReturn Nothing = mzero
        maybeReturn (Just x) = return x
  parseJSON _ = mzero

实际上我认为我可以从1开始计数并获得顶点,而程序仍然会解析每个i。但这不是一个好的选择,因为最小vertex id并不总是1,有时下一个vertex id与当前的1不同。甚至可以解析这些数据吗?无论如何,我最简单地解决了这个问题(当vertex ids1开始并使用(+1)递增时)。

好的。这就是我如何获得max和min顶点id:

import qualified Data.Text.Read                as TR
import qualified Data.Foldable                 as Foldable

minID :: [Either T.Text Int] -> Int
minID = Foldable.maximum

maxID :: [Either T.Text Int] -> Int
maxID = Foldable.minimum

ids :: Object -> [Either T.Text Int]
ids o = map ((fmap fst) . TR.decimal) (M.keys o)

所有签名都不是一般化的,但这只是一个例子。

我将在明天再次尝试解决这个简单的问题。无论如何,主要问题仍然需要答案:)

2 个答案:

答案 0 :(得分:1)

对答案的编辑表明您了解如何解决您的问题。尽管如此,通过避免构建顶点所需的大多数显式列表操作,您可以使代码更加清晰。计划是:

  • FromJSON;
  • 定义Point个实例
  • 使用它为FromJSON定义Vertex个实例。对于你链接到的问题,这就像Rule中的case实例一样,除了因为你想将对象键用作ID,case M.toList (o :: Object) of [(rawID, rawPoint)] -> Vertex (TR.decimal rawId) <$> parseJSON rawPoint _ -> fail "Rule: unexpected format" 语句会变成某种东西像:

    FromJSON Graph
  • 最后,如果您将[{1}}的(推断的)类型更改为vs,我认为您现有的[Vertex]实例会立即工作,因为实例{{ 1}}。因此,您不再需要FromJSON a => FromJSON [a]

如果您可以控制JSON结构,那么通过使顶点ID成为parseVerticesx旁边的字段来进一步简化操作可能是有意义的,从而删除一级嵌套。

更新:实例的实现,基于您添加到答案中的实例:

y

other answer

与您的版本的不同之处是:

  • 您无需为instance FromJSON Point where parseJSON (Object v) = liftM2 Point (v .: "x") (v .: "y") parseJSON _ = fail "Bad point" instance FromJSON [Vertex] where parseJSON j = case j of (Object o) -> mapM parseVertex $ M.toList o _ -> fail "Bad vertices" where parseVertex (rawID, rawPoint) = do let eID = TR.decimal rawID liftM2 Vertex (either (fail "Bad vertex id") (return . fst) eID) $ parseJSON rawPoint instance FromJSON Graph where parseJSON (Object v) = do i <- parseJSON =<< v .: "id" vs <- parseJSON =<< v .: "vertices" sc <- parseJSON =<< v .: "scenario" return $ Graph i vs sc parseJSON _ = fail "Bad graph" 定义实例;如果您定义[Graph]实例,则aeson将自动处理列表(即JS数组)(请注意Get the implementation as a runnable example文档提到Graph实例。不幸的是我们不能这样做(至少不会因为顶点ID是键而不是值的一部分,所以FromJSON a => FromJSON [a]很容易。{/ li>
  • 我为模式匹配失败添加了[Vertex]个案例,以便获取更多信息性错误消息。
  • 关于从fail值创建顶点的观察结果:您的解决方案非常合理。我只使用Either(来自either)重构了它,以便提供自定义错误消息。

值得一提的是,如果使用applicative样式编写,Data.Either(或liftM2等)代码往往看起来更好。例如,liftM3实例中的有趣案例可能变为:

Point

答案 1 :(得分:0)

我刚刚为简单案例实施了解决方案。这是源代码:

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

instance FromJSON Graph where
  parseJSON (Object v) = do
    i <- parseJSON =<< v .: "id"
    vs <- parseJSON =<< v .: "vertices"
    sc <- parseJSON =<< v .: "scenario"
    buildGraph i sc <$> concat <$> parseVertices vs
      where
        parseVertices v@(Object o) = parseFromTo minID maxID v
          where
            minID = unpackIndex $ Foldable.minimum ids
            maxID = unpackIndex $ Foldable.maximum ids
            unpackIndex eitherI = case eitherI of
              Right i -> i
              Left e -> error e
            ids = map ((fmap fst) . TR.decimal) (M.keys o)

        parseVertex i v = do
          p1 <- v .:* [(T.pack $ show i), "x"]
          p2 <- v .:* [(T.pack $ show i), "y"]
          return $ vertex i p1 p2

        parseFromTo i j v | i == j = return []
                          | otherwise = do
          vertex <- parseVertex i v
          liftM2 (:) (return [vertex]) (parseFromTo (i + 1) j v)

        buildGraph :: Int -> Scenario -> [Vertex] -> Graph
        buildGraph i sc vertices = Graph i vertices sc

  parseJSON _ = mzero

功能lookupE(.:*)来自Petr Pudlák&#39; s answer

我不太喜欢parseJSON函数的这种实现。但它适用于我的顶点具有delta 1的id的情况。我知道我无法从Foldable.minimum idsFoldable.maximum ids中提取值,但它已经把我带到了monad地狱(一点点)。

所以这是一个json文件的例子,在解析后我们得到了Nothing

{
    "id": 1,
    "vertices": {
        "3": {
            "y": 12,
            "x": 0
        },
        "2": {
            "y": 16,
            "x": 24
        },
        "1": {
            "y": 12,
            "x": 10
        }
    },
    "scenario": [
        1,
        2,
        3,
        1
    ]
}

所以我现在打开这个问题。

<强>更新

哦,我刚看到自己的错误。我已经拥有了所有钥匙。 :)

ids = map ((fmap fst) . TR.decimal) (M.keys o)

现在我把这个问题暂时开了几天。也许有人会改进我的解决方案。

更新2

感谢duplode,我让代码更清晰,更易读。

以下是来源:

instance FromJSON Point where
  parseJSON (Object v) = liftM2 Point (v .: "x") (v .: "y")

instance FromJSON [Vertex] where
  parseJSON (Object o) = mapM parseVertex $ M.toList o
    where
      parseVertex (rawID, rawPoint) = Vertex (fromRight . (fmap fst) . TR.decimal $ rawID) <$> parseJSON rawPoint

instance FromJSON Graph where
  parseJSON (Object v) = do
    i <- parseJSON =<< v .: "id"
    vs <- parseJSON =<< v .: "vertices"
    sc <- parseJSON =<< v .: "scenario"
    return $ Graph i vs sc

instance FromJSON [Graph] where
  parseJSON (Object o) = mapM parseGraph $ M.toList o
    where
      parseGraph (_, rawGraph) = parseJSON rawGraph

我不需要任何辅助函数来提取嵌套值。

顺便说一句,我不知道有什么更好的方法来创建Vertex而不是Vertex (fromRight . (fmap fst) . TR.decimal $ rawID) <$> parseJSON rawPoint。无法使用liftM2,因为第二个参数的类型为Either a b,但第三个参数的类型为Parser c。无法结合:)