Haskell Aeson для работы с отсутствующими данными

У меня есть (действительный) массив с кодировкой json, в котором отсутствуют или искажены данные. Я хочу, чтобы Эсон превратил это в Maybe [Maybe Point] и получил Nothing, где элемент массива не был допустимым Point.

import Data.Aeson
decode "[1,2,3,4,null,\"hello\"]" :: (Maybe [Maybe Int])
=> Nothing

Но я бы предпочел, чтобы он оценивал

=> Just [Just 1, Just 2, Just 3, Just 4, Nothing, Nothing]

Если это невозможно сделать с помощью Aeson, есть ли другая библиотека, которая может это сделать?

Обратите внимание, что фактический объект намного сложнее, чем простое целое число, поэтому манипуляции со строками не являются жизнеспособным решением.


person fakedrake    schedule 25.11.2015    source источник


Ответы (3)


Я бы использовал значения и работал с ними:

decode "[1,2,3,4,null,\"hello\"]" :: (Maybe [Value])
Just [Number 1.0,Number 2.0,Number 3.0,Number 4.0,Null,String "hello"]

So

> let fromNum v = case v of Number x -> Just x ; _ -> Nothing 
> maybe [] (map fromNum) (decode  "[1,2,3,4,null,\"hello\"]" :: (Maybe [Value])
[Just 1.0,Just 2.0,Just 3.0,Just 4.0,Nothing,Nothing]

Очень неэффективным (но безопасным) решением будет:

> let tmp = maybe [] (map fromNum) (decode  "[1,2,3,4,null,\"hello\"]" :: (Maybe [Value])
> let keepJust l v = case v of Just i -> i:l; Nothing -> l
> reverse (foldl keepJust [] tmp)
[1.0,2.0,3.0,4.0]

Если вы хотите сделать вещи более эффективными, вы можете использовать Vectors и foldl'.

person yogsototh    schedule 25.11.2015

Мы можем создать новый тип вокруг Maybe, который не даст сбой при синтаксическом анализе, но вместо этого будет успешным с Nothing:

{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving #-}

import Data.Aeson
import Data.Coerce
import Control.Applicative
import Control.Monad

newtype Maybe' a = Maybe' (Maybe a) deriving
  (Eq, Ord, Show, Functor, Applicative, Monad, Alternative, MonadPlus)

instance FromJSON a => FromJSON (Maybe' a) where
  parseJSON v = do
    case fromJSON v :: Result a of
      Success a -> pure (Maybe' $ Just a)
      _         -> pure (Maybe' $ Nothing)

Теперь мы можем обернуть типы с помощью Maybe', чтобы получить желаемое поведение:

> decode "[4, 5, \"foo\", \"bar\"]" :: Maybe [Maybe' Int]
Just [Maybe' (Just 4),Maybe' (Just 5),Maybe' Nothing,Maybe' Nothing]

Однако есть большая вероятность, что впоследствии мы захотим работать с обычными значениями Maybe. Data.Coerce здесь пригодится, поскольку позволяет принудить все Maybe' к Maybe, независимо от того, где они находятся в типе результата:

> coerce (decode "[[[], 3], [4, 5]]" :: Maybe [(Maybe' Int, Int)]) :: Maybe [(Maybe Int, Int)]
Just [(Nothing,3),(Just 4,5)]
person András Kovács    schedule 25.11.2015
comment
Мне нравится этот ответ. Я понятия не имел о Data.Coerce. Я выбрал подход @yogsototh, потому что в нем меньше кода, но спасибо! - person fakedrake; 25.11.2015

Начнем с того, что ваш ожидаемый результат относится не к типу (Maybe [Maybe Int]), а к типу [Maybe Int].

Во-вторых, по определению decode это невозможно. Тем не менее, вы можете использовать decode для декодирования массива JSON в список значений Aeson, которые затем вы можете сопоставить и декодировать фактические значения.

Таким образом, ошибка синтаксического анализа для одного значения не приводит к ошибке синтаксического анализа для всего массива.

Изменить: поскольку вы изменили свои ожидания, это может помочь вам:

Prelude Data.ByteString.Lazy.Char8 Data.Aeson> (decode (pack "[1,2,3,null,\"hello\"]") :: Maybe [Value])
Just [Number 1.0,Number 2.0,Number 3.0,Null,String "hello"]

Оттуда вы можете отображать и сопоставлять так, чтобы это соответствовало вашим потребностям (потому что простое округление с плавающей запятой может быть неприемлемым).

person Tobi Nary    schedule 25.11.2015
comment
... вам все равно придется обернуть fromMaybe [] вокруг него, чтобы получить ожидаемый результат. - person Tobi Nary; 25.11.2015