Значение вектора соответствия шаблона в Data.Aeson
Я использую Data.Aeson для анализа JSON для моего пользовательского типа. Я пытаюсь сопоставить образец Vector Value
(Array
) в моем FromJSON
Например, но не знаю, как я могу это сделать. 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)
Могу ли я использовать сопоставление с образцом в массиве для обработки этого случая? или я должен вручную проверить тип каждого Value
? и как с этим?
1 ответ
Нет, вы не можете соответствовать шаблону так, как вы пытаетесь сделать здесь. Массив JSON может содержать значения разных типов, и вы не можете сопоставить шаблон со всеми значениями в списке, такими как они, если они есть.
Есть несколько способов решить вашу актуальную проблему здесь. Есть простой способ, и есть явный способ, который даст вам лучшие сообщения об ошибках.
Простой способ
Самый простой способ - использовать тот факт, что он уже существует. FromJSON
случаи для Text
а также [a]
, Из-за этого вы можете использовать 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"