Data.Aeson 中的模式匹配向量值
Pattern Match Vector Value in Data.Aeson
我正在使用 Data.Aeson 将 JSON 解析为我的自定义类型。
我尝试在 FromJSON
实例中进行模式匹配 Vector Value
(Array
),但不知道该怎么做。 JSON value
键值可以是 String
、String
列表或 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
的类型?怎么办?
不,您不能按照您在此处尝试执行的方式进行模式匹配。 JSON 数组可以包含不同类型的值,并且您不能像在一个列表中那样对列表中的所有值进行模式匹配。
这里有几种方法可以解决您的实际问题。有一种简单的方法,还有一种显式的方法可以给你更好的错误信息。
简单的方法
最简单的方法是利用 Text
和 [a]
已经存在 FromJSON
个实例这一事实。因此,您可以使用 Alternative
运算符来编写您的实例,如下所示:
instance FromJSON Foo where
parseJSON v = (SimpleText <$> parseJSON v)
<|> (ListOfText <$> parseJSON v)
<|> (ListOfListOfText <$> parseJSON v)
这里的技巧是 Aeson 首先会尝试解析一个 Text
值,然后如果失败它会尝试 [Text]
,如果再次失败它会尝试 [[Text]]
.
此解决方案的问题在于,如果您的 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"
我正在使用 Data.Aeson 将 JSON 解析为我的自定义类型。
我尝试在 FromJSON
实例中进行模式匹配 Vector Value
(Array
),但不知道该怎么做。 JSON value
键值可以是 String
、String
列表或 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
的类型?怎么办?
不,您不能按照您在此处尝试执行的方式进行模式匹配。 JSON 数组可以包含不同类型的值,并且您不能像在一个列表中那样对列表中的所有值进行模式匹配。
这里有几种方法可以解决您的实际问题。有一种简单的方法,还有一种显式的方法可以给你更好的错误信息。
简单的方法
最简单的方法是利用 Text
和 [a]
已经存在 FromJSON
个实例这一事实。因此,您可以使用 Alternative
运算符来编写您的实例,如下所示:
instance FromJSON Foo where
parseJSON v = (SimpleText <$> parseJSON v)
<|> (ListOfText <$> parseJSON v)
<|> (ListOfListOfText <$> parseJSON v)
这里的技巧是 Aeson 首先会尝试解析一个 Text
值,然后如果失败它会尝试 [Text]
,如果再次失败它会尝试 [[Text]]
.
此解决方案的问题在于,如果您的 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"