我使用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 ids
从1
开始并使用(+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)
所有签名都不是一般化的,但这只是一个例子。
我将在明天再次尝试解决这个简单的问题。无论如何,主要问题仍然需要答案:)
答案 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成为parseVertices
和x
旁边的字段来进一步简化操作可能是有意义的,从而删除一级嵌套。
更新:实例的实现,基于您添加到答案中的实例:
y
与您的版本的不同之处是:
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 ids
和Foldable.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
。无法结合:)