1

カスタムタイプaesonのjsonファイルを生成および解析するためにライブラリを使用しています。Graphここに型定義があります。

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ファイルからの解析に問題があります。主な問題は、特定のグラフがいくつの頂点にあるかわからないため、ハードコーディングできないことです。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ん。そのようなデータを解析することさえ可能ですか? とにかく、私はこの問題の最も単純なケースでも立ち往生しました(から開始して を使用してインクリメントする場合)。1vertex id1vertex ids1(+1)

大丈夫。これは、最大頂点 ID と最小頂点 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)

すべての署名が一般化されているわけではありませんが、これは単なる例です。

この単純な問題のケースを解決するために、明日もう一度試してみます。とにかく、主な質問にはまだ答えが必要です:)

4

2 に答える 2

1

回答を編集すると、差し迫った問題を解決する方法を理解したことがわかります。それでも、頂点を構築するために必要な明示的なリスト操作のほとんどを回避することで、コードをより明確にすることができます。計画は次のとおりです。

  • FromJSONのインスタンスを定義しますPoint
  • FromJSONのインスタンスを定義するために使用しますVertexこれは、リンク先の質問に対する他の回答Ruleのインスタンスとかなり似ていますが、オブジェクト キーを ID として使用する必要があるため、ステートメントは次のようになります。case

    case M.toList (o :: Object) of
        [(rawID, rawPoint)] -> Vertex (TR.decimal rawId) <$> parseJSON rawPoint
        _                   -> fail "Rule: unexpected format"
    
  • 最後に、既存のインスタンスは、(推定された) 型をinstanceFromJSON Graphに変更すると、すぐに機能すると思います。したがって、もう必要ありません。vs[Vertex]FromJSON a => FromJSON [a]parseVertices

xJSON 構造を制御できる場合は、頂点 ID をとの横のフィールドにしてy、1 レベルのネストを削除することで、さらに単純化することが理にかなっています。

更新: 回答に追加したものに基づくインスタンスの実装:

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]。インスタンスを定義すると、Graphaeson はリスト (すなわち JS 配列) を自動的に処理します ( FromJSONのドキュメントではFromJSON a => FromJSON [a]インスタンスについて言及されていることに注意してください。残念ながら[Vertex]、頂点 ID がキーであり、値の一部。
  • failより有益なエラー メッセージを取得するために、パターン マッチの失敗のケースを追加しました。
  • 値から頂点を作成することについてのあなたの観察では、Eitherあなたの解決策はかなり合理的でした。カスタムエラーメッセージを提供するためにeither(from )を使用してリファクタリングしただけです。Data.Either

liftM2(またはなど) コードは、アプリカliftM3ティブ スタイルを使用して記述した場合に見栄えがよくなる傾向があることに注意してください。たとえば、インスタンスの興味深いケースは次のようにPointなります。

parseJSON (Object v) = Point <$> v .: "x" <*> v .: "y"
于 2014-03-28T12:53:43.457 に答える
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回答からのものです。

この関数の実装はあまり好きではありませんparseJSON。しかし、私の頂点がデルタ 1 の id を持っている場合には機能します。Foldable.minimum idsandから値を抽出できなかったことはわかってFoldable.maximum idsいますが、モナド地獄 (小さな地獄) に陥りました。

これは、解析後の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 (fromRight . (fmap fst) . TR.decimal $ rawID) <$> parseJSON rawPoint。2 番目の引数の typeが 3liftM2番目の引数の type であるため、使用できません。結合できません:)Either a bParser c

于 2014-03-28T07:29:45.763 に答える