Data.Aeson中的模式匹配矢量值

时间:2015-06-09 20:38:31

标签: haskell aeson

我正在使用Data.Aeson将JSON解析为我的自定义类型。 我尝试在我的Vector Value实例中模式匹配ArrayFromJSON),但不知道我该怎么做。 JSON value密钥的值为StringString列表或String列表列表。

instance FromJSON Foo where
  parseJSON (Object o) =
    case lookup "value" o of
      Just (String s) -> pure $ SimpleText s
      Just foo@(Array (String s)) -> pure $ ListOfText $ V.toList <$> V.mapM (parseJSON :: Value -> Parser Text) foo
      Just foo@(Array (Array (String s))) -> pure $ ListOfListOfText $ V.toList <$> V.mapM (parseJSON :: Value -> Parser Text) $ V.toList <$> V.mapM (parseJSON :: Value -> [Parser Value]) foo

data Foo = SimpleText Text
         | ListOfText [Text]
         | ListOfListOfText [[Text]]
         deriving (Show, Eq, Ord)

我可以在Array上使用模式匹配来处理这种情况吗?或者我应该手动检查每个Value的类型?怎么做?

1 个答案:

答案 0 :(得分:3)

不,你不能按照你在这里尝试的方式进行模式匹配。 JSON数组可以包含不同类型的值,并且您不能对列表中的所有值进行模式匹配,就像它们在一个值中一样。

有几种方法可以解决您的实际问题。有一种简单方法,并且有一种显式方式可以为您提供更好的错误消息。

简单方法

简单的方法是使用FromJSONText已存在[a]个实例的事实。因此,您可以使用Alternative运算符来编写您的实例,如下所示:

instance FromJSON Foo where
    parseJSON v =  (SimpleText <$> parseJSON v) 
               <|> (ListOfText <$> parseJSON v) 
               <|> (ListOfListOfText <$> parseJSON v)

这里的诀窍是Aeson首先会尝试解析Text值,然后如果失败则会尝试[Text],如果再次失败则会尝试[[Text]]。< / p>

此解决方案的问题在于,如果您的JSON格式错误,则错误消息可能没有意义。例如,如果你给它一个顶级Null值,你的错误将是它期望[[Text]],因为你总是得到链中最后一个值的错误。

显式方式

要获得更好的错误消息,您必须更加关注您期望的值。如果结果是空数组,如果它是ListOfText还是ListOfListOfText怎么办?由于我们无法直接在Vector上进行模式匹配,因此我们可以将其转换为列表和模式匹配:

instance FromJSON Foo where
    parseJSON v = case v of
        -- If its a string, we return the string as a SimpleText
        (String s) -> return $ SimpleText s

        -- If its an array, we turn the vector to a list so we can pattern match on it
        (Array a)  -> case V.toList a of

            -- If its a empty list, we return a empty ListOfText
            []             -> return $ ListOfText []

            -- If the first value is a string, we put it as the first element of our ListOfTexts and try to parse the rest.
            (String s: xs) -> ListOfText . (s:) <$> mapM parseJSON xs

            -- If the first value is an array, we try to parse it as [Text], then parse the rest.
            (Array a: xa)  -> ListOfListOfText <$> ((:) <$> parseJSON (Array a) <*> mapM parseJSON xa)

            -- If the first value is neither a string or array we return a error message.
            _              -> fail "Expected an Array or an Array of Arrays."

        -- If the top level value is not a string or array we return a error message.
        _ -> fail "Expected a String or Array"
相关问题