{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE Rank2Types #-}
#if __GLASGOW_HASKELL__ >= 800
{-# LANGUAGE TemplateHaskellQuotes #-}
#else
{-# LANGUAGE TemplateHaskell #-}
#endif
module Data.Aeson.Types.Internal
(
Value(..)
, Array
, emptyArray, isEmptyArray
, Pair
, Object
, emptyObject
, Parser
, Result(..)
, IResult(..)
, JSONPathElement(..)
, JSONPath
, iparse
, parse
, parseEither
, parseMaybe
, modifyFailure
, prependFailure
, parserThrowError
, parserCatchError
, formatError
, (<?>)
, object
, Options(
fieldLabelModifier
, constructorTagModifier
, allNullaryToStringTag
, omitNothingFields
, sumEncoding
, unwrapUnaryRecords
, tagSingleConstructors
)
, SumEncoding(..)
, JSONKeyOptions(keyModifier)
, defaultOptions
, defaultTaggedObject
, defaultJSONKeyOptions
, camelTo
, camelTo2
, DotNetTime(..)
) where
import Prelude.Compat
import Control.Applicative (Alternative(..))
import Control.Arrow (first)
import Control.DeepSeq (NFData(..))
import Control.Monad (MonadPlus(..), ap)
import Data.Char (isLower, isUpper, toLower, isAlpha, isAlphaNum)
import Data.Data (Data)
import Data.Foldable (foldl')
import Data.HashMap.Strict (HashMap)
import Data.Hashable (Hashable(..))
import Data.List (intercalate)
import Data.Scientific (Scientific)
import Data.Semigroup (Semigroup((<>)))
import Data.String (IsString(..))
import Data.Text (Text, pack, unpack)
import Data.Time (UTCTime)
import Data.Time.Format (FormatTime)
import Data.Typeable (Typeable)
import Data.Vector (Vector)
import GHC.Generics (Generic)
import qualified Control.Monad as Monad
import qualified Control.Monad.Fail as Fail
import qualified Data.HashMap.Strict as H
import qualified Data.Scientific as S
import qualified Data.Vector as V
import qualified Language.Haskell.TH.Syntax as TH
#if !MIN_VERSION_unordered_containers(0,2,6)
import Data.List (sort)
#endif
data JSONPathElement = Key Text
| Index {-# UNPACK #-} !Int
deriving (Eq, Show, Typeable, Ord)
type JSONPath = [JSONPathElement]
data IResult a = IError JSONPath String
| ISuccess a
deriving (Eq, Show, Typeable)
data Result a = Error String
| Success a
deriving (Eq, Show, Typeable)
instance NFData JSONPathElement where
rnf (Key t) = rnf t
rnf (Index i) = rnf i
instance (NFData a) => NFData (IResult a) where
rnf (ISuccess a) = rnf a
rnf (IError path err) = rnf path `seq` rnf err
instance (NFData a) => NFData (Result a) where
rnf (Success a) = rnf a
rnf (Error err) = rnf err
instance Functor IResult where
fmap f (ISuccess a) = ISuccess (f a)
fmap _ (IError path err) = IError path err
{-# INLINE fmap #-}
instance Functor Result where
fmap f (Success a) = Success (f a)
fmap _ (Error err) = Error err
{-# INLINE fmap #-}
instance Monad.Monad IResult where
return = pure
{-# INLINE return #-}
ISuccess a >>= k = k a
IError path err >>= _ = IError path err
{-# INLINE (>>=) #-}
#if !(MIN_VERSION_base(4,13,0))
fail = Fail.fail
{-# INLINE fail #-}
#endif
instance Fail.MonadFail IResult where
fail err = IError [] err
{-# INLINE fail #-}
instance Monad.Monad Result where
return = pure
{-# INLINE return #-}
Success a >>= k = k a
Error err >>= _ = Error err
{-# INLINE (>>=) #-}
#if !(MIN_VERSION_base(4,13,0))
fail = Fail.fail
{-# INLINE fail #-}
#endif
instance Fail.MonadFail Result where
fail err = Error err
{-# INLINE fail #-}
instance Applicative IResult where
pure = ISuccess
{-# INLINE pure #-}
(<*>) = ap
{-# INLINE (<*>) #-}
instance Applicative Result where
pure = Success
{-# INLINE pure #-}
(<*>) = ap
{-# INLINE (<*>) #-}
instance MonadPlus IResult where
mzero = fail "mzero"
{-# INLINE mzero #-}
mplus a@(ISuccess _) _ = a
mplus _ b = b
{-# INLINE mplus #-}
instance MonadPlus Result where
mzero = fail "mzero"
{-# INLINE mzero #-}
mplus a@(Success _) _ = a
mplus _ b = b
{-# INLINE mplus #-}
instance Alternative IResult where
empty = mzero
{-# INLINE empty #-}
(<|>) = mplus
{-# INLINE (<|>) #-}
instance Alternative Result where
empty = mzero
{-# INLINE empty #-}
(<|>) = mplus
{-# INLINE (<|>) #-}
instance Semigroup (IResult a) where
(<>) = mplus
{-# INLINE (<>) #-}
instance Monoid (IResult a) where
mempty = fail "mempty"
{-# INLINE mempty #-}
mappend = (<>)
{-# INLINE mappend #-}
instance Semigroup (Result a) where
(<>) = mplus
{-# INLINE (<>) #-}
instance Monoid (Result a) where
mempty = fail "mempty"
{-# INLINE mempty #-}
mappend = (<>)
{-# INLINE mappend #-}
instance Foldable IResult where
foldMap _ (IError _ _) = mempty
foldMap f (ISuccess y) = f y
{-# INLINE foldMap #-}
foldr _ z (IError _ _) = z
foldr f z (ISuccess y) = f y z
{-# INLINE foldr #-}
instance Foldable Result where
foldMap _ (Error _) = mempty
foldMap f (Success y) = f y
{-# INLINE foldMap #-}
foldr _ z (Error _) = z
foldr f z (Success y) = f y z
{-# INLINE foldr #-}
instance Traversable IResult where
traverse _ (IError path err) = pure (IError path err)
traverse f (ISuccess a) = ISuccess <$> f a
{-# INLINE traverse #-}
instance Traversable Result where
traverse _ (Error err) = pure (Error err)
traverse f (Success a) = Success <$> f a
{-# INLINE traverse #-}
type Failure f r = JSONPath -> String -> f r
type Success a f r = a -> f r
newtype Parser a = Parser {
runParser :: forall f r.
JSONPath
-> Failure f r
-> Success a f r
-> f r
}
instance Monad.Monad Parser where
m >>= g = Parser $ \path kf ks -> let ks' a = runParser (g a) path kf ks
in runParser m path kf ks'
{-# INLINE (>>=) #-}
return = pure
{-# INLINE return #-}
#if !(MIN_VERSION_base(4,13,0))
fail = Fail.fail
{-# INLINE fail #-}
#endif
instance Fail.MonadFail Parser where
fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
{-# INLINE fail #-}
instance Functor Parser where
fmap f m = Parser $ \path kf ks -> let ks' a = ks (f a)
in runParser m path kf ks'
{-# INLINE fmap #-}
instance Applicative Parser where
pure a = Parser $ \_path _kf ks -> ks a
{-# INLINE pure #-}
(<*>) = apP
{-# INLINE (<*>) #-}
instance Alternative Parser where
empty = fail "empty"
{-# INLINE empty #-}
(<|>) = mplus
{-# INLINE (<|>) #-}
instance MonadPlus Parser where
mzero = fail "mzero"
{-# INLINE mzero #-}
mplus a b = Parser $ \path kf ks -> let kf' _ _ = runParser b path kf ks
in runParser a path kf' ks
{-# INLINE mplus #-}
instance Semigroup (Parser a) where
(<>) = mplus
{-# INLINE (<>) #-}
instance Monoid (Parser a) where
mempty = fail "mempty"
{-# INLINE mempty #-}
mappend = (<>)
{-# INLINE mappend #-}
apP :: Parser (a -> b) -> Parser a -> Parser b
apP d e = do
b <- d
b <$> e
{-# INLINE apP #-}
type Object = HashMap Text Value
type Array = Vector Value
data Value = Object !Object
| Array !Array
| String !Text
| Number !Scientific
| Bool !Bool
| Null
deriving (Eq, Read, Show, Typeable, Data, Generic)
newtype DotNetTime = DotNetTime {
fromDotNetTime :: UTCTime
} deriving (Eq, Ord, Read, Show, Typeable, FormatTime)
instance NFData Value where
rnf (Object o) = rnf o
rnf (Array a) = foldl' (\x y -> rnf y `seq` x) () a
rnf (String s) = rnf s
rnf (Number n) = rnf n
rnf (Bool b) = rnf b
rnf Null = ()
instance IsString Value where
fromString = String . pack
{-# INLINE fromString #-}
hashValue :: Int -> Value -> Int
#if MIN_VERSION_unordered_containers(0,2,6)
hashValue s (Object o) = s `hashWithSalt` (0::Int) `hashWithSalt` o
#else
hashValue s (Object o) = foldl' hashWithSalt
(s `hashWithSalt` (0::Int)) assocHashesSorted
where
assocHashesSorted = sort [hash k `hashWithSalt` v | (k, v) <- H.toList o]
#endif
hashValue s (Array a) = foldl' hashWithSalt
(s `hashWithSalt` (1::Int)) a
hashValue s (String str) = s `hashWithSalt` (2::Int) `hashWithSalt` str
hashValue s (Number n) = s `hashWithSalt` (3::Int) `hashWithSalt` n
hashValue s (Bool b) = s `hashWithSalt` (4::Int) `hashWithSalt` b
hashValue s Null = s `hashWithSalt` (5::Int)
instance Hashable Value where
hashWithSalt = hashValue
instance TH.Lift Value where
lift Null = [| Null |]
lift (Bool b) = [| Bool b |]
lift (Number n) = [| Number (S.scientific c e) |]
where
c = S.coefficient n
e = S.base10Exponent n
lift (String t) = [| String (pack s) |]
where s = unpack t
lift (Array a) = [| Array (V.fromList a') |]
where a' = V.toList a
lift (Object o) = [| Object (H.fromList . map (first pack) $ o') |]
where o' = map (first unpack) . H.toList $ o
emptyArray :: Value
emptyArray = Array V.empty
isEmptyArray :: Value -> Bool
isEmptyArray (Array arr) = V.null arr
isEmptyArray _ = False
emptyObject :: Value
emptyObject = Object H.empty
parse :: (a -> Parser b) -> a -> Result b
parse m v = runParser (m v) [] (const Error) Success
{-# INLINE parse #-}
iparse :: (a -> Parser b) -> a -> IResult b
iparse m v = runParser (m v) [] IError ISuccess
{-# INLINE iparse #-}
parseMaybe :: (a -> Parser b) -> a -> Maybe b
parseMaybe m v = runParser (m v) [] (\_ _ -> Nothing) Just
{-# INLINE parseMaybe #-}
parseEither :: (a -> Parser b) -> a -> Either String b
parseEither m v = runParser (m v) [] onError Right
where onError path msg = Left (formatError path msg)
{-# INLINE parseEither #-}
formatError :: JSONPath -> String -> String
formatError path msg = "Error in " ++ format "$" path ++ ": " ++ msg
where
format :: String -> JSONPath -> String
format pfx [] = pfx
format pfx (Index idx:parts) = format (pfx ++ "[" ++ show idx ++ "]") parts
format pfx (Key key:parts) = format (pfx ++ formatKey key) parts
formatKey :: Text -> String
formatKey key
| isIdentifierKey strKey = "." ++ strKey
| otherwise = "['" ++ escapeKey strKey ++ "']"
where strKey = unpack key
isIdentifierKey :: String -> Bool
isIdentifierKey [] = False
isIdentifierKey (x:xs) = isAlpha x && all isAlphaNum xs
escapeKey :: String -> String
escapeKey = concatMap escapeChar
escapeChar :: Char -> String
escapeChar '\'' = "\\'"
escapeChar '\\' = "\\\\"
escapeChar c = [c]
type Pair = (Text, Value)
object :: [Pair] -> Value
object = Object . H.fromList
{-# INLINE object #-}
(<?>) :: Parser a -> JSONPathElement -> Parser a
p <?> pathElem = Parser $ \path kf ks -> runParser p (pathElem:path) kf ks
modifyFailure :: (String -> String) -> Parser a -> Parser a
modifyFailure f (Parser p) = Parser $ \path kf ks ->
p path (\p' m -> kf p' (f m)) ks
prependFailure :: String -> Parser a -> Parser a
prependFailure = modifyFailure . (++)
parserThrowError :: JSONPath -> String -> Parser a
parserThrowError path' msg = Parser $ \path kf _ks ->
kf (reverse path ++ path') msg
parserCatchError :: Parser a -> (JSONPath -> String -> Parser a) -> Parser a
parserCatchError (Parser p) handler = Parser $ \path kf ks ->
p path (\e msg -> runParser (handler e msg) path kf ks) ks
data Options = Options
{ fieldLabelModifier :: String -> String
, constructorTagModifier :: String -> String
, allNullaryToStringTag :: Bool
, omitNothingFields :: Bool
, sumEncoding :: SumEncoding
, unwrapUnaryRecords :: Bool
, tagSingleConstructors :: Bool
}
instance Show Options where
show (Options f c a o s u t) =
"Options {"
++ intercalate ", "
[ "fieldLabelModifier =~ " ++ show (f "exampleField")
, "constructorTagModifier =~ " ++ show (c "ExampleConstructor")
, "allNullaryToStringTag = " ++ show a
, "omitNothingFields = " ++ show o
, "sumEncoding = " ++ show s
, "unwrapUnaryRecords = " ++ show u
, "tagSingleConstructors = " ++ show t
]
++ "}"
data SumEncoding =
TaggedObject { tagFieldName :: String
, contentsFieldName :: String
}
| UntaggedValue
| ObjectWithSingleField
| TwoElemArray
deriving (Eq, Show)
data JSONKeyOptions = JSONKeyOptions
{ keyModifier :: String -> String
}
defaultOptions :: Options
defaultOptions = Options
{ fieldLabelModifier = id
, constructorTagModifier = id
, allNullaryToStringTag = True
, omitNothingFields = False
, sumEncoding = defaultTaggedObject
, unwrapUnaryRecords = False
, tagSingleConstructors = False
}
defaultTaggedObject :: SumEncoding
defaultTaggedObject = TaggedObject
{ tagFieldName = "tag"
, contentsFieldName = "contents"
}
defaultJSONKeyOptions :: JSONKeyOptions
defaultJSONKeyOptions = JSONKeyOptions id
camelTo :: Char -> String -> String
{-# DEPRECATED camelTo "Use camelTo2 for better results" #-}
camelTo c = lastWasCap True
where
lastWasCap :: Bool
-> String
-> String
lastWasCap _ [] = []
lastWasCap prev (x : xs) = if isUpper x
then if prev
then toLower x : lastWasCap True xs
else c : toLower x : lastWasCap True xs
else x : lastWasCap False xs
camelTo2 :: Char -> String -> String
camelTo2 c = map toLower . go2 . go1
where go1 "" = ""
go1 (x:u:l:xs) | isUpper u && isLower l = x : c : u : l : go1 xs
go1 (x:xs) = x : go1 xs
go2 "" = ""
go2 (l:u:xs) | isLower l && isUpper u = l : c : u : go2 xs
go2 (x:xs) = x : go2 xs