Значение вектора соответствия шаблона в 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"
Другие вопросы по тегам