Skip to content

Commit a8cb1f8

Browse files
committed
Added aeson compatibile decoding functions.
1 parent 5267696 commit a8cb1f8

File tree

4 files changed

+87
-17
lines changed

4 files changed

+87
-17
lines changed

.travis.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@ matrix:
66
addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
77
- env: CABALVER=1.22 GHCVER=7.10.1
88
addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.1],sources: [hvr-ghc]}}
9-
# - env: CABALVER=head GHCVER=head
10-
# addons: {apt: {packages: [cabal-install-head,ghc-head], sources: [hvr-ghc]}}
11-
# allow_failures:
12-
# - env: CABALVER=head GHCVER=head
9+
- env: CABALVER=1.24 GHCVER=8.0.1
10+
addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.1], sources: [hvr-ghc]}}
11+
allow_failures:
12+
- env: CABALVER=1.24 GHCVER=8.0.1
1313

1414
before_install:
1515
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
1616
- ghc --version
1717
- cabal --version
18-
- cabal update
1918

2019
install:
21-
- "cabal install --only-dependencies --enable-tests"
20+
- travis_retry cabal update
21+
- sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
22+
- cabal install --only-dependencies --enable-tests
2223

2324
script:
24-
- "cabal configure --enable-tests && cabal build && cabal test"
25+
- cabal configure --enable-tests
26+
- cabal build
27+
- cabal test
28+
- cabal check
29+
- cabal sdist
2530

2631
notifications:
2732
email: true

Data/JsonStream/CLexer.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE BangPatterns #-}
2+
{-# LANGUAGE CPP #-}
23
{-# LANGUAGE ForeignFunctionInterface #-}
34
{-# LANGUAGE MultiWayIf #-}
45
{-# LANGUAGE OverloadedStrings #-}
@@ -9,7 +10,10 @@ module Data.JsonStream.CLexer (
910
, unescapeText
1011
) where
1112

13+
#if !MIN_VERSION_bytestring(0,10,6)
1214
import Control.Applicative ((<$>))
15+
#endif
16+
1317
import Control.Monad (when)
1418
import qualified Data.Aeson as AE
1519
import qualified Data.ByteString as BSW

Data/JsonStream/Parser.hs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE BangPatterns #-}
2+
{-# LANGUAGE CPP #-}
23
{-# LANGUAGE OverloadedStrings #-}
34
{-# LANGUAGE PatternGuards #-}
45
{-# LANGUAGE ScopedTypeVariables #-}
@@ -39,6 +40,11 @@ module Data.JsonStream.Parser (
3940
, runParser'
4041
, parseByteString
4142
, parseLazyByteString
43+
-- * Aeson in-place replacement functions
44+
, decode
45+
, eitherDecode
46+
, decodeStrict
47+
, eitherDecodeStrict
4248
-- * FromJSON parser
4349
, value
4450
, string
@@ -71,12 +77,17 @@ module Data.JsonStream.Parser (
7177
, objectFound
7278
) where
7379

80+
#if !MIN_VERSION_bytestring(0,10,6)
81+
import Data.Monoid (Monoid, mappend, mempty)
82+
#endif
83+
7484
import Control.Applicative
7585
import qualified Data.Aeson as AE
76-
import qualified Data.ByteString as BS
77-
import qualified Data.ByteString.Lazy as BL
86+
import qualified Data.ByteString.Char8 as BS
87+
import qualified Data.ByteString.Lazy.Char8 as BL
88+
import qualified Data.ByteString.Lazy.Internal as BL
89+
import Data.Char (isSpace)
7890
import qualified Data.HashMap.Strict as HMap
79-
import Data.Monoid (Monoid, mappend, mempty)
8091
import Data.Scientific (Scientific, isInteger,
8192
toBoundedInteger, toRealFloat)
8293
import qualified Data.Text as T
@@ -116,9 +127,11 @@ instance Functor Parser where
116127
yieldResults :: [a] -> ParseResult a -> ParseResult a
117128
yieldResults values end = foldr Yield end values
118129

119-
-- | '<*>' will run both parsers in parallel and combine results. It
120-
-- behaves as a list functor (produces all combinations), but the typical
130+
-- | '<*>' will run both parsers in parallel and combine results.
131+
--
132+
-- It behaves as a list functor (produces all combinations), but the typical
121133
-- use is:
134+
--
122135
-- >>> :set -XOverloadedStrings
123136
-- >>> let text = "[{\"name\": \"John\", \"age\": 20}, {\"age\": 30, \"name\": \"Frank\"}]"
124137
-- >>> let parser = arrayOf $ (,) <$> "name" .: string <*> "age" .: integer
@@ -636,16 +649,64 @@ parseByteString parser startdata = loop (runParser' parser startdata)
636649

637650
-- | Parse a lazy bytestring, generate lazy list of parsed values. If an error occurs, throws an exception.
638651
parseLazyByteString :: Parser a -> BL.ByteString -> [a]
639-
parseLazyByteString parser input = loop chunks (runParser parser)
652+
parseLazyByteString parser input = loop input (runParser parser)
640653
where
641-
chunks = BL.toChunks input
642-
loop [] (ParseNeedData _) = error "Not enough data."
643-
loop (dta:rest) (ParseNeedData np) = loop rest (np dta)
654+
loop BL.Empty (ParseNeedData _) = error "Not enough data."
655+
loop (BL.Chunk dta rest) (ParseNeedData np) = loop rest (np dta)
644656
loop _ (ParseDone _) = []
645657
loop _ (ParseFailed err) = error err
646658
loop rest (ParseYield v np) = v : loop rest np
647659

648660

661+
-- | Deserialize a JSON value from lazy 'BL.ByteString'.
662+
--
663+
-- If this fails due to incomplete or invalid input, 'Nothing' is returned.
664+
--
665+
-- The input must consist solely of a JSON document, with no trailing data except for whitespace.
666+
decode :: AE.FromJSON a => BL.ByteString -> Maybe a
667+
decode bs =
668+
case eitherDecode bs of
669+
Right val -> Just val
670+
Left _ -> Nothing
671+
672+
-- | Like 'decode' but returns an error message when decoding fails.
673+
eitherDecode :: AE.FromJSON a => BL.ByteString -> Either String a
674+
eitherDecode bs = loop bs (runParser value)
675+
where
676+
loop BL.Empty (ParseNeedData _) = Left "Not enough data."
677+
loop (BL.Chunk dta rest) (ParseNeedData np) = loop rest (np dta)
678+
loop _ (ParseDone _) = Left "Nothing parsed."
679+
loop _ (ParseFailed err) = Left err
680+
loop rest (ParseYield v next) = checkExit v next rest
681+
682+
checkExit v (ParseDone srest) rest
683+
| BS.all isSpace srest && BL.all isSpace rest = Right v
684+
| otherwise = Left "Data followed by non-whitespace characters."
685+
checkExit _ (ParseYield _ _) _ = Left "Multiple value parses?"
686+
checkExit _ (ParseFailed err) _ = Left err
687+
checkExit _ (ParseNeedData _) BL.Empty = Left "Incomplete json structure."
688+
checkExit v (ParseNeedData cont) (BL.Chunk dta rest) = checkExit v (cont dta) rest
689+
690+
-- | Like 'decode', but on strict 'BS.ByteString'
691+
decodeStrict :: AE.FromJSON a => BS.ByteString -> Maybe a
692+
decodeStrict bs =
693+
case eitherDecodeStrict bs of
694+
Right val -> Just val
695+
Left _ -> Nothing
696+
697+
-- | Like 'eitherDecode', but on strict 'BS.ByteString'
698+
eitherDecodeStrict :: AE.FromJSON a => BS.ByteString -> Either String a
699+
eitherDecodeStrict bs =
700+
case runParser' value bs of
701+
ParseYield next v -> checkExit v next
702+
ParseNeedData _ -> Left "Incomplete json structure."
703+
ParseFailed err -> Left err
704+
ParseDone _ -> Left "No data found."
705+
where
706+
checkExit (ParseDone rest) v
707+
| BS.all isSpace rest = Right v
708+
checkExit _ _ = Left "Data folowed by non-whitespace characters."
709+
649710
-- $use
650711
--
651712
-- >>> parseByteString value "[1,2,3]" :: [[Int]]

json-stream.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: json-stream
2-
version: 0.4.0.0
2+
version: 0.4.1.0
33
synopsis: Incremental applicative JSON parser
44
description: Easy to use JSON parser fully supporting incremental parsing.
55
Parsing grammar in applicative form.

0 commit comments

Comments
 (0)