Haskell Aeson to deal with missing data

2019-07-15 07:02发布

问题:

I have a (valid) json encoded array that has missing or malformed data. I want Aeson to turn that into Maybe [Maybe Point] and have Nothing where the array element was not a valid Point.

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

But I would much rather have it evaluate to

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

If this can't be done with Aeson, is there another library that can do it?

Note that the actual object is much more complex than a simple integer so string manipulations are not a viable solution.

回答1:

I would use Values and work from them:

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]

A very ineffective (but safe) solution would be:

> 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]

If you want to make things more effective you might want to use Vectors and foldl'.



回答2:

We can create a newtype around Maybe that doesn't fail when parsing fails, but instead succeeds with 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)

Now we can wrap types with Maybe' to get the desired behavior:

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

However, there's a good chance we want to work with regular Maybe values afterwards. Data.Coerce comes handy here, since it lets us coerce all Maybe'-s to Maybe-s, no matter where they are in the result type:

> coerce (decode "[[[], 3], [4, 5]]" :: Maybe [(Maybe' Int, Int)]) :: Maybe [(Maybe Int, Int)]
Just [(Nothing,3),(Just 4,5)]


回答3:

To begin with, your expected result is not of type (Maybe [Maybe Int]), but of type [Maybe Int].

Secondly, as of the definition of decode, this is not possible. Yet, you could use decode to decode the JSON array into a list of Aeson Values, which then you can map over and decode the actual values.

Thus, a parser error for a single value does not result in a parser error for the whole array.

Edit: As you modified your expectations, this might help you:

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"]

From there, you can map and match such that it fits your needs (because just rounding floats may not be suiteable).



标签: haskell aeson