|
1 | 1 | {-# LANGUAGE BangPatterns #-} |
| 2 | +{-# LANGUAGE CPP #-} |
2 | 3 | {-# LANGUAGE OverloadedStrings #-} |
3 | 4 | {-# LANGUAGE PatternGuards #-} |
4 | 5 | {-# LANGUAGE ScopedTypeVariables #-} |
@@ -39,6 +40,11 @@ module Data.JsonStream.Parser ( |
39 | 40 | , runParser' |
40 | 41 | , parseByteString |
41 | 42 | , parseLazyByteString |
| 43 | + -- * Aeson in-place replacement functions |
| 44 | + , decode |
| 45 | + , eitherDecode |
| 46 | + , decodeStrict |
| 47 | + , eitherDecodeStrict |
42 | 48 | -- * FromJSON parser |
43 | 49 | , value |
44 | 50 | , string |
@@ -71,12 +77,17 @@ module Data.JsonStream.Parser ( |
71 | 77 | , objectFound |
72 | 78 | ) where |
73 | 79 |
|
| 80 | +#if !MIN_VERSION_bytestring(0,10,6) |
| 81 | +import Data.Monoid (Monoid, mappend, mempty) |
| 82 | +#endif |
| 83 | + |
74 | 84 | import Control.Applicative |
75 | 85 | 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) |
78 | 90 | import qualified Data.HashMap.Strict as HMap |
79 | | -import Data.Monoid (Monoid, mappend, mempty) |
80 | 91 | import Data.Scientific (Scientific, isInteger, |
81 | 92 | toBoundedInteger, toRealFloat) |
82 | 93 | import qualified Data.Text as T |
@@ -116,9 +127,11 @@ instance Functor Parser where |
116 | 127 | yieldResults :: [a] -> ParseResult a -> ParseResult a |
117 | 128 | yieldResults values end = foldr Yield end values |
118 | 129 |
|
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 |
121 | 133 | -- use is: |
| 134 | +-- |
122 | 135 | -- >>> :set -XOverloadedStrings |
123 | 136 | -- >>> let text = "[{\"name\": \"John\", \"age\": 20}, {\"age\": 30, \"name\": \"Frank\"}]" |
124 | 137 | -- >>> let parser = arrayOf $ (,) <$> "name" .: string <*> "age" .: integer |
@@ -636,16 +649,64 @@ parseByteString parser startdata = loop (runParser' parser startdata) |
636 | 649 |
|
637 | 650 | -- | Parse a lazy bytestring, generate lazy list of parsed values. If an error occurs, throws an exception. |
638 | 651 | parseLazyByteString :: Parser a -> BL.ByteString -> [a] |
639 | | -parseLazyByteString parser input = loop chunks (runParser parser) |
| 652 | +parseLazyByteString parser input = loop input (runParser parser) |
640 | 653 | 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) |
644 | 656 | loop _ (ParseDone _) = [] |
645 | 657 | loop _ (ParseFailed err) = error err |
646 | 658 | loop rest (ParseYield v np) = v : loop rest np |
647 | 659 |
|
648 | 660 |
|
| 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 | + |
649 | 710 | -- $use |
650 | 711 | -- |
651 | 712 | -- >>> parseByteString value "[1,2,3]" :: [[Int]] |
|
0 commit comments