可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 Vector
s 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).