Skip to content

Commit b1099ec

Browse files
committed
Add verboseDecode variants with accumulative errors
1 parent e1bce4b commit b1099ec

File tree

4 files changed

+66
-2
lines changed

4 files changed

+66
-2
lines changed

Data/Aeson.hs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ module Data.Aeson
3737
, decode'
3838
, eitherDecode
3939
, eitherDecode'
40+
, verboseDecode
41+
, verboseDecode'
4042
, encode
4143
-- ** Variants for strict bytestrings
4244
, decodeStrict
4345
, decodeStrict'
4446
, eitherDecodeStrict
4547
, eitherDecodeStrict'
48+
, verboseDecodeStrict
49+
, verboseDecodeStrict'
4650
-- * Core JSON types
4751
, Value(..)
4852
, Encoding
@@ -130,9 +134,14 @@ import Prelude.Compat
130134

131135
import Data.Aeson.Types.FromJSON (ifromJSON)
132136
import Data.Aeson.Encoding (encodingToLazyByteString)
133-
import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, eitherDecodeWith, eitherDecodeStrictWith, jsonEOF, json, jsonEOF', json')
137+
import Data.Aeson.Parser.Internal
138+
( decodeWith, decodeStrictWith
139+
, eitherDecodeWith, eitherDecodeStrictWith
140+
, verboseDecodeWith, verboseDecodeStrictWith
141+
, jsonEOF, json, jsonEOF', json')
134142
import Data.Aeson.Types
135-
import Data.Aeson.Types.Internal (JSONPath, formatError)
143+
import Data.Aeson.Types.Internal (JSONPath, formatError, formatErrors)
144+
import Data.List.NonEmpty (NonEmpty)
136145
import qualified Data.ByteString as B
137146
import qualified Data.ByteString.Lazy as L
138147

@@ -220,6 +229,35 @@ eitherDecodeStrict' =
220229
eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON
221230
{-# INLINE eitherDecodeStrict' #-}
222231

232+
eitherFormatErrors
233+
:: Either (NonEmpty (JSONPath, String)) a -> Either (NonEmpty String) a
234+
eitherFormatErrors = either (Left . formatErrors) Right
235+
{-# INLINE eitherFormatErrors #-}
236+
237+
-- | Like 'decode' but returns one or more error messages when decoding fails.
238+
verboseDecode :: (FromJSON a) => L.ByteString -> Either (NonEmpty String) a
239+
verboseDecode = eitherFormatErrors . verboseDecodeWith jsonEOF ifromJSON
240+
{-# INLINE verboseDecode #-}
241+
242+
-- | Like 'decodeStrict' but returns one or more error messages when decoding
243+
-- fails.
244+
verboseDecodeStrict :: (FromJSON a) => B.ByteString -> Either (NonEmpty String) a
245+
verboseDecodeStrict =
246+
eitherFormatErrors . verboseDecodeStrictWith jsonEOF ifromJSON
247+
{-# INLINE verboseDecodeStrict #-}
248+
249+
-- | Like 'decode'' but returns one or more error messages when decoding fails.
250+
verboseDecode' :: (FromJSON a) => L.ByteString -> Either (NonEmpty String) a
251+
verboseDecode' = eitherFormatErrors . verboseDecodeWith jsonEOF' ifromJSON
252+
{-# INLINE verboseDecode' #-}
253+
254+
-- | Like 'decodeStrict'' but returns one or more error messages when decoding
255+
-- fails.
256+
verboseDecodeStrict' :: (FromJSON a) => B.ByteString -> Either (NonEmpty String) a
257+
verboseDecodeStrict' =
258+
eitherFormatErrors . verboseDecodeStrictWith jsonEOF' ifromJSON
259+
{-# INLINE verboseDecodeStrict' #-}
260+
223261
-- $use
224262
--
225263
-- This section contains basic information on the different ways to

Data/Aeson/Internal.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module Data.Aeson.Internal
1818
, JSONPath
1919
, (<?>)
2020
, formatError
21+
, formatErrors
2122
, ifromJSON
2223
, iparse
2324
) where

Data/Aeson/Parser/Internal.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ module Data.Aeson.Parser.Internal
3333
, decodeStrictWith
3434
, eitherDecodeWith
3535
, eitherDecodeStrictWith
36+
, verboseDecodeWith
37+
, verboseDecodeStrictWith
3638
) where
3739

3840
import Prelude ()
@@ -288,6 +290,24 @@ eitherDecodeStrictWith p to s =
288290
IError (e :| _) -> Left e
289291
{-# INLINE eitherDecodeStrictWith #-}
290292

293+
verboseDecodeWith :: Parser Value -> (Value -> IResult a) -> L.ByteString
294+
-> Either (NonEmpty (JSONPath, String)) a
295+
verboseDecodeWith p to s =
296+
case L.parse p s of
297+
L.Done _ v -> case to v of
298+
ISuccess a -> Right a
299+
IError e -> Left e
300+
L.Fail _ _ msg -> Left (([], msg) :| [])
301+
{-# INLINE verboseDecodeWith #-}
302+
303+
verboseDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
304+
-> Either (NonEmpty (JSONPath, String)) a
305+
verboseDecodeStrictWith p to s =
306+
case either (\e -> IError (([], e) :| [])) to (A.parseOnly p s) of
307+
ISuccess a -> Right a
308+
IError e -> Left e
309+
{-# INLINE verboseDecodeStrictWith #-}
310+
291311
-- $lazy
292312
--
293313
-- The 'json' and 'value' parsers decouple identification from

Data/Aeson/Types/Internal.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ module Data.Aeson.Types.Internal
4949
, parserCatchError
5050
, parserCatchErrors
5151
, formatError
52+
, formatErrors
5253
, (<?>)
5354
-- * Constructors and accessors
5455
, object
@@ -484,6 +485,10 @@ formatError path msg = "Error in " ++ format "$" path ++ ": " ++ msg
484485
escapeChar '\\' = "\\\\"
485486
escapeChar c = [c]
486487

488+
-- | Annotate a list of error messages.
489+
formatErrors :: Functor f => f (JSONPath, String) -> f String
490+
formatErrors = fmap (uncurry formatError)
491+
487492
-- | A key\/value pair for an 'Object'.
488493
type Pair = (Text, Value)
489494

0 commit comments

Comments
 (0)