aeson-extra 0.2.3.0 → 0.3.0.0
raw patch · 7 files changed
+67/−429 lines, 7 filesdep +aeson-compatdep +base-compatdep +parsecdep −attoparsecdep ~time
Dependencies added: aeson-compat, base-compat, parsec, time-parsers
Dependencies removed: attoparsec
Dependency ranges changed: time
Files
- CHANGELOG.md +6/−0
- README.md +5/−4
- aeson-extra.cabal +13/−15
- src/Data/Aeson/Compat.hs +0/−194
- src/Data/Aeson/Extra.hs +43/−32
- src/Data/Aeson/Extra/Time.hs +0/−159
- src/Data/Time/TH.hs +0/−25
CHANGELOG.md view
@@ -1,3 +1,9 @@+# 0.3.0.0 (2015-12-25)++- `Data.Time.TH` moved to [`time-parsers`](http://hackage.haskell.org/package/time-parsers)+- `Data.Aeson.Compat` moved to [`aeson-compat`](http://hackage.haskell.org/package/aeson-compat)+- The modules aren't re-exported, as that would require `Cabal >= 1.21` restriction+ # 0.2.3.0 (2015-12-08) - `Data.Time.TH` module with `mkUTCTime`
README.md view
@@ -2,8 +2,9 @@ [](https://travis-ci.org/phadej/aeson-extra) [](http://hackage.haskell.org/package/aeson-extra)--The package motivation is twofold:+[](http://stackage.org/lts-3/package/aeson-extra)+[](http://stackage.org/nightly/package/aeson-extra) -- provide compatibility layer for [`aeson`](http://hackage.haskell.org/package/aeson)-- provide extra combinators+Package provides extra funcitonality on top of+[`aeson`](https://hackage.haskell.org/package/aeson) and+[`aeson-compat`](https://hackage.haskell.org/package/aeson-compat)
aeson-extra.cabal view
@@ -3,13 +3,9 @@ -- see: https://github.com/sol/hpack name: aeson-extra-version: 0.2.3.0+version: 0.3.0.0 synopsis: Extra goodies for aeson-description: The package motivation is twofold:- .- * provide compatibility layer for @aeson@- .- * provide extra combinators+description: Package provides extra funcitonality on top of @aeson@ and @aeson-compat@ category: Web homepage: https://github.com/phadej/aeson-extra#readme bug-reports: https://github.com/phadej/aeson-extra/issues@@ -17,7 +13,7 @@ maintainer: Oleg Grenrus <oleg.grenrus@iki.fi> license: BSD3 license-file: LICENSE-tested-with: GHC==7.6.3, GHC==7.8.4, GHC==7.10.2+tested-with: GHC==7.6.3, GHC==7.8.4, GHC==7.10.3 build-type: Simple cabal-version: >= 1.10 @@ -35,24 +31,23 @@ ghc-options: -Wall build-depends: base >=4.6 && <4.9+ , base-compat >=0.6.0 && <0.9 , aeson >=0.7.0.6 && <0.11- , attoparsec >=0.12 && <0.14+ , aeson-compat >=0.3.0.0 && <0.4 , bytestring >=0.10 && <0.11 , containers >=0.5 && <0.6 , exceptions >=0.8 && <0.9 , hashable >=1.2 && <1.3+ , parsec >=3.1.9 && <3.2 , scientific >=0.3 && <0.4 , template-haskell >=2.8 && <2.11 , text >=1.2 && <1.3- , time >=1.4.2 && <1.6+ , time >=1.4.2 && <1.7+ , time-parsers >=0.1.0.0 && <0.2 , unordered-containers >=0.2 && <0.3 , vector >=0.10 && <0.12 exposed-modules:- Data.Aeson.Compat Data.Aeson.Extra- Data.Time.TH- other-modules:- Data.Aeson.Extra.Time default-language: Haskell2010 test-suite aeson-extra-test@@ -63,16 +58,19 @@ ghc-options: -Wall build-depends: base >=4.6 && <4.9+ , base-compat >=0.6.0 && <0.9 , aeson >=0.7.0.6 && <0.11- , attoparsec >=0.12 && <0.14+ , aeson-compat >=0.3.0.0 && <0.4 , bytestring >=0.10 && <0.11 , containers >=0.5 && <0.6 , exceptions >=0.8 && <0.9 , hashable >=1.2 && <1.3+ , parsec >=3.1.9 && <3.2 , scientific >=0.3 && <0.4 , template-haskell >=2.8 && <2.11 , text >=1.2 && <1.3- , time >=1.4.2 && <1.6+ , time >=1.4.2 && <1.7+ , time-parsers >=0.1.0.0 && <0.2 , unordered-containers >=0.2 && <0.3 , vector >=0.10 && <0.12 , aeson-extra
− src/Data/Aeson/Compat.hs
@@ -1,194 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--------------------------------------------------------------------------------- |--- Module : Data.Aeson.Compat--- Copyright : (C) 2015 Oleg Grenrus--- License : BSD3--- Maintainer : Oleg Grenrus <oleg.grenrus@iki.fi>------ Compatibility notices------ * 'decode' etc. work as in @aeson >=0.9@--- * but it is generalised to work in any 'MonadThrow' (that is extra)--- * '.:?' works as in @aeson <0.10@--- * '.:!' works as '.:?' in @aeson ==0.10@-module Data.Aeson.Compat (- -- * Generic decoding functions- decode,- decode',- decodeStrict,- decodeStrict',- AesonException(..),- -- * Either decoding functions- eitherDecode, eitherDecode', eitherDecodeStrict, eitherDecodeStrict',- -- * Operators- (.:?), (.:!),- -- * Re-exports- -- | Original 'Data.Aeson..:?' operator is not re-exported- module Data.Aeson,- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative-#endif--#if MIN_VERSION_aeson(0,10,0)-import Data.Monoid-#endif--import Data.Aeson hiding- ((.:?), decode, decode', decodeStrict, decodeStrict'-#if !MIN_VERSION_aeson (0,9,0)- , eitherDecode, eitherDecode', eitherDecodeStrict, eitherDecodeStrict'-#endif- )--#if !MIN_VERSION_aeson (0,9,0)-import Data.Aeson.Parser (value, value')-import qualified Data.Attoparsec.ByteString as A-import qualified Data.Attoparsec.ByteString.Char8 as A (skipSpace)-import qualified Data.Attoparsec.Lazy as L-#endif--import Control.Monad.Catch-import Data.Aeson.Types hiding ((.:?))-import Data.ByteString as B-import Data.ByteString.Lazy as L-import qualified Data.HashMap.Strict as H-import Data.Text as T-import Data.Typeable (Typeable)--#if !MIN_VERSION_aeson(0,10,0)-import Data.Time (Day, LocalTime)-import qualified Data.Aeson.Extra.Time as ExtraTime-#endif---- | Exception thrown by 'decode' - family of functions in this module.-newtype AesonException = AesonException String- deriving (Show, Typeable)--instance Exception AesonException--eitherAesonExc :: (MonadThrow m) => Either String a -> m a-eitherAesonExc (Left err) = throwM (AesonException err)-eitherAesonExc (Right x) = return x---- | Like original 'Data.Aeson.decode' but in arbitrary 'MonadThrow'.------ Parse a top-level JSON value, i.e. also strings, numbers etc.-decode :: (FromJSON a, MonadThrow m) => L.ByteString -> m a-decode = eitherAesonExc . eitherDecode---- | Like original 'Data.Aeson.decode'' but in arbitrary 'MonadThrow'.-decode' :: (FromJSON a, MonadThrow m) => L.ByteString -> m a-decode' = eitherAesonExc . eitherDecode'---- | Like original 'Data.Aeson.decodeStrict' but in arbitrary 'MonadThrow'.-decodeStrict :: (FromJSON a, MonadThrow m) => B.ByteString -> m a-decodeStrict = eitherAesonExc . eitherDecodeStrict---- | Like original 'Data.Aeson.decodeStrict'' but in arbitrary 'MonadThrow'.-decodeStrict' :: (FromJSON a, MonadThrow m) => B.ByteString -> m a-decodeStrict' = eitherAesonExc . eitherDecodeStrict'---- | Retrieve the value associated with the given key of an 'Object'.--- The result is 'Nothing' if the key is not present, or 'empty' if--- the value cannot be converted to the desired type.------ This accessor is most useful if the key and value can be absent--- from an object without affecting its validity. If the key and--- value are mandatory, use '.:' instead.------ This operator is consistent in 'aeson >=0.8 && <0.11'-(.:?) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)-obj .:? key = case H.lookup key obj of- Nothing -> pure Nothing- Just v ->-#if MIN_VERSION_aeson(0,10,0)- modifyFailure addKeyName $ parseJSON v -- <?> Key key- where- addKeyName = (("failed to parse field " <> T.unpack key <> ": ") <>)-#else- parseJSON v-#endif-{-# INLINE (.:?) #-}---- | Like '.:?', but the resulting parser will fail,--- if the key is present but is 'Null'.-(.:!) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)-obj .:! key = case H.lookup key obj of- Nothing -> pure Nothing- Just v ->-#if MIN_VERSION_aeson(0,10,0)- modifyFailure addKeyName $ Just <$> parseJSON v -- <?> Key key- where- addKeyName = (("failed to parse field " <> T.unpack key <> ": ") <>)-#else- Just <$> parseJSON v-#endif-{-# INLINE (.:!) #-}--#if !MIN_VERSION_aeson(0,9,0)--- From Parser.Internal---- | Parse a top-level JSON value followed by optional whitespace and--- end-of-input. See also: 'json'.-jsonEOF :: A.Parser Value-jsonEOF = value <* A.skipSpace <* A.endOfInput---- | Parse a top-level JSON value followed by optional whitespace and--- end-of-input. See also: 'json''.-jsonEOF' :: A.Parser Value-jsonEOF' = value' <* A.skipSpace <* A.endOfInput---- | Like 'decode' but returns an error message when decoding fails.-eitherDecode :: (FromJSON a) => L.ByteString -> Either String a-eitherDecode = eitherDecodeWith jsonEOF fromJSON-{-# INLINE eitherDecode #-}---- | Like 'decodeStrict' but returns an error message when decoding fails.-eitherDecodeStrict :: (FromJSON a) => B.ByteString -> Either String a-eitherDecodeStrict = eitherDecodeStrictWith jsonEOF fromJSON-{-# INLINE eitherDecodeStrict #-}---- | Like 'decode'' but returns an error message when decoding fails.-eitherDecode' :: (FromJSON a) => L.ByteString -> Either String a-eitherDecode' = eitherDecodeWith jsonEOF' fromJSON-{-# INLINE eitherDecode' #-}---- | Like 'decodeStrict'' but returns an error message when decoding fails.-eitherDecodeStrict' :: (FromJSON a) => B.ByteString -> Either String a-eitherDecodeStrict' = eitherDecodeStrictWith jsonEOF' fromJSON-{-# INLINE eitherDecodeStrict' #-}--eitherDecodeWith :: L.Parser Value -> (Value -> Result a) -> L.ByteString- -> Either String a-eitherDecodeWith p to s =- case L.parse p s of- L.Done _ v -> case to v of- Success a -> Right a- Error msg -> Left msg- L.Fail _ _ msg -> Left msg-{-# INLINE eitherDecodeWith #-}--eitherDecodeStrictWith :: A.Parser Value -> (Value -> Result a) -> B.ByteString- -> Either String a-eitherDecodeStrictWith p to s =- case either Error to (A.parseOnly p s) of- Success a -> Right a- Error msg -> Left msg-{-# INLINE eitherDecodeStrictWith #-}--#endif--#if !MIN_VERSION_aeson(0,10,0)--- | /Since: aeson-extra-0.2.2.0/-instance FromJSON Day where- parseJSON = withText "Day" (ExtraTime.run ExtraTime.day)---- | /Since: aeson-extra-0.2.2.0/-instance FromJSON LocalTime where- parseJSON = withText "LocalTime" (ExtraTime.run ExtraTime.localTime)-#endif
src/Data/Aeson/Extra.hs view
@@ -1,14 +1,14 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Aeson.Extra@@ -46,36 +46,39 @@ module Data.Aeson.Compat, ) where -#if !MIN_VERSION_base(4,8,0)-import Data.Foldable (Foldable)-import Data.Traversable (Traversable, traverse)-#endif+import Prelude ()+import Prelude.Compat -import Control.Applicative-import Data.Monoid-import qualified Data.ByteString as BS+import Control.Applicative (Alternative(..))+import Data.Aeson.Compat+import Data.Aeson.Types hiding ((.:?))+import Data.Hashable (Hashable)+import Data.Monoid+import Data.Text (Text)+import Data.Time (UTCTime, ZonedTime)++import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS-import Data.Aeson.Compat-import Data.Aeson.Types hiding ((.:?))-import qualified Data.Foldable as Foldable-import qualified Data.HashMap.Strict as H-import Data.Hashable (Hashable)-import qualified Data.Map as Map-import Data.Text as T-import qualified Data.Text.Lazy as TL-import Data.Time (UTCTime, ZonedTime)-import qualified Data.Text.Read as T+import qualified Data.Foldable as Foldable+import qualified Data.HashMap.Strict as H+import qualified Data.Map as Map+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Read as T #if MIN_VERSION_base(4,7,0)-import Data.Proxy-import GHC.TypeLits+import Data.Proxy+import GHC.TypeLits #endif #if !MIN_VERSION_aeson (0,10,0)-import qualified Data.Aeson.Extra.Time as ExtraTime+import qualified Text.Parsec as Parsec+import qualified Data.Time.Parsers as TimeParsers #endif -- | Like 'encode', but produces strict 'BS.ByteString'.+--+-- /Since: 0.2.3.0/ encodeStrict :: ToJSON a => a -> BS.ByteString encodeStrict = LBS.toStrict . encode @@ -293,7 +296,7 @@ #if MIN_VERSION_aeson (0,10,0) parseJSON = fmap U . parseJSON #else- parseJSON = withText "UTCTime" (fmap U . ExtraTime.run ExtraTime.utcTime)+ parseJSON = withText "UTCTime" (fmap U . run TimeParsers.utcTime) #endif -- | A type to parse 'ZonedTime'@@ -312,5 +315,13 @@ #if MIN_VERSION_aeson (0,10,0) parseJSON = fmap Z . parseJSON #else- parseJSON = withText "ZonedTime" (fmap Z . ExtraTime.run ExtraTime.zonedTime)+ parseJSON = withText "ZonedTime" (fmap Z . run TimeParsers.zonedTime)+#endif++#if !MIN_VERSION_aeson (0,10,0)+-- | Run a 'parsers' parser as an aeson parser.+run :: Parsec.Parsec Text () a -> Text -> Parser a+run p t = case Parsec.parse (p <* Parsec.eof) "" t of+ Left err -> fail $ "could not parse date: " ++ show err+ Right r -> return r #endif
− src/Data/Aeson/Extra/Time.hs
@@ -1,159 +0,0 @@-{-# LANGUAGE BangPatterns, CPP, ScopedTypeVariables #-}---- |--- Module: Data.Aeson.Extra.Time (Data.Aeson.Parser.Time)--- Copyright: (c) 2015 Bryan O'Sullivan--- License: Apache--- Maintainer: Bryan O'Sullivan <bos@serpentine.com>--- Stability: experimental--- Portability: portable------ Parsers for parsing dates and times.--module Data.Aeson.Extra.Time- (- run- , day- , localTime- , timeOfDay- , timeZone- , utcTime- , zonedTime- ) where--import Control.Monad (when, void)-import Data.Attoparsec.Text as A-import Data.Bits ((.&.))-import Data.Char (isDigit, ord)-import Data.Fixed (Pico)-import Data.Int (Int64)-import Data.Maybe (fromMaybe)-import Data.Text (Text)-import Data.Time.Calendar (Day, fromGregorianValid)-import Data.Time.Clock (UTCTime(..))-import qualified Data.Aeson.Types as Aeson-import qualified Data.Text as T-import qualified Data.Time.LocalTime as Local--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>), (<*), (*>))-#endif---- from Data.Aeson.Internal.Time-import Unsafe.Coerce (unsafeCoerce)--toPico :: Integer -> Pico-toPico = unsafeCoerce---- | Run an attoparsec parser as an aeson parser.-run :: Parser a -> Text -> Aeson.Parser a-run p t = case A.parseOnly (p <* endOfInput) t of- Left err -> fail $ "could not parse date: " ++ err- Right r -> return r---- | Parse a date of the form @YYYY-MM-DD@.-day :: Parser Day-day = do- y <- decimal <* char '-'- m <- twoDigits <* char '-'- d <- twoDigits- maybe (fail "invalid date") return (fromGregorianValid y m d)---- | Parse a two-digit integer (e.g. day of month, hour).-twoDigits :: Parser Int-twoDigits = do- a <- digit- b <- digit- let c2d c = ord c .&. 15- return $! c2d a * 10 + c2d b---- | Parse a time of the form @HH:MM:SS[.SSS]@.-timeOfDay :: Parser Local.TimeOfDay-timeOfDay = do- h <- twoDigits <* char ':'- m <- twoDigits <* char ':'- s <- seconds- if h < 24 && m < 60 && s < 61- then return (Local.TimeOfDay h m s)- else fail "invalid time"--data T = T {-# UNPACK #-} !Int {-# UNPACK #-} !Int64---- | Parse a count of seconds, with the integer part being two digits--- long.-seconds :: Parser Pico-seconds = do- real <- twoDigits- mc <- peekChar- case mc of- Just '.' -> do- t <- anyChar *> takeWhile1 isDigit- return $! parsePicos real t- _ -> return $! fromIntegral real- where- parsePicos a0 t = toPico (fromIntegral (t' * 10^n))- where T n t' = T.foldl' step (T 12 (fromIntegral a0)) t- step ma@(T m a) c- | m <= 0 = ma- | otherwise = T (m-1) (10 * a + fromIntegral (ord c) .&. 15)---- | Parse a time zone, and return 'Nothing' if the offset from UTC is--- zero. (This makes some speedups possible.)-timeZone :: Parser (Maybe Local.TimeZone)-timeZone = do- let maybeSkip c = do ch <- peekChar'; when (ch == c) (void anyChar)- maybeSkip ' '- ch <- satisfy $ \c -> c == 'Z' || c == '+' || c == '-'- if ch == 'Z'- then return Nothing- else do- h <- twoDigits- mm <- peekChar- m <- case mm of- Just ':' -> anyChar *> twoDigits- Just d | isDigit d -> twoDigits- _ -> return 0- let off | ch == '-' = negate off0- | otherwise = off0- off0 = h * 60 + m- case undefined of- _ | off == 0 ->- return Nothing- | off < -720 || off > 840 || m > 59 ->- fail "invalid time zone offset"- | otherwise ->- let !tz = Local.minutesToTimeZone off- in return (Just tz)---- | Parse a date and time, of the form @YYYY-MM-DD HH:MM:SS@.--- The space may be replaced with a @T@. The number of seconds may be--- followed by a fractional component.-localTime :: Parser Local.LocalTime-localTime = Local.LocalTime <$> day <* daySep <*> timeOfDay- where daySep = satisfy (\c -> c == 'T' || c == ' ')---- | Behaves as 'zonedTime', but converts any time zone offset into a--- UTC time.-utcTime :: Parser UTCTime-utcTime = do- lt@(Local.LocalTime d t) <- localTime- mtz <- timeZone- case mtz of- Nothing -> let !tt = Local.timeOfDayToTime t- in return (UTCTime d tt)- Just tz -> return $! Local.localTimeToUTC tz lt---- | Parse a date with time zone info. Acceptable formats:------ @YYYY-MM-DD HH:MM:SS Z@------ The first space may instead be a @T@, and the second space is--- optional. The @Z@ represents UTC. The @Z@ may be replaced with a--- time zone offset of the form @+0000@ or @-08:00@, where the first--- two digits are hours, the @:@ is optional and the second two digits--- (also optional) are minutes.-zonedTime :: Parser Local.ZonedTime-zonedTime = Local.ZonedTime <$> localTime <*> (fromMaybe utc <$> timeZone)--utc :: Local.TimeZone-utc = Local.TimeZone 0 False ""
− src/Data/Time/TH.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}--- | Template Haskell extras for `Data.Time`.------ This module is in a wrong place, but it reuses the same parser as aeson parser.-module Data.Time.TH (mkUTCTime) where--import Control.Applicative ((<*))-import Data.Time (UTCTime(..), Day(..))-import qualified Data.Text as T-import qualified Data.Attoparsec.Text as A-import Language.Haskell.TH-import Data.Aeson.Extra.Time (utcTime)---- | Make a 'UTCTime'. Accepts the same strings as aeson `UTCTime` parser accepts.------ > t :: UTCTime--- > t = $(mkUTCTime "2014-05-12 00:02:03.456000Z")-mkUTCTime :: String -> Q Exp-mkUTCTime s =- case A.parseOnly (utcTime <* A.endOfInput) (T.pack s) of- Left err -> fail $ "could not parse UTCTime: " ++ err- Right (UTCTime (ModifiedJulianDay d) dt) ->- [| UTCTime (ModifiedJulianDay $(d')) $(dt') :: UTCTime |]- where d' = return $ LitE $ integerL d- dt' = return $ LitE $ rationalL $ toRational dt