#if __GLASGOW_HASKELL__ < 710
#endif
module Database.PostgreSQL.Typed.Types
(
OID
, PGValue(..)
, PGValues
, PGTypeName(..)
, PGTypeEnv(..)
, unknownPGTypeEnv
, PGRecord(..)
, PGType(..)
, PGParameter(..)
, PGColumn(..)
, pgEncodeParameter
, pgEscapeParameter
, pgDecodeColumn
, pgDecodeColumnNotNull
, pgQuote
, pgDQuote
, parsePGDQuote
, buildPGValue
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>), (<$), (<*), (*>))
#endif
#ifdef USE_AESON
import qualified Data.Aeson as JSON
#endif
import qualified Data.Attoparsec.ByteString as P (anyWord8)
import qualified Data.Attoparsec.ByteString.Char8 as P
import Data.Bits (shiftL, (.|.))
import Data.ByteString.Internal (w2c)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BSB
import qualified Data.ByteString.Builder.Prim as BSBP
import qualified Data.ByteString.Char8 as BSC
import Data.ByteString.Internal (c2w)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.UTF8 as BSU
import Data.Char (isSpace, isDigit, digitToInt, intToDigit, toLower)
import Data.Int
import Data.List (intersperse)
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid (mempty, mconcat)
#endif
import Data.Ratio ((%), numerator, denominator)
#ifdef USE_SCIENTIFIC
import Data.Scientific (Scientific)
#endif
#ifdef USE_TEXT
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
#endif
import qualified Data.Time as Time
#if MIN_VERSION_time(1,5,0)
import Data.Time (defaultTimeLocale)
#else
import System.Locale (defaultTimeLocale)
#endif
#ifdef USE_UUID
import qualified Data.UUID as UUID
#endif
import Data.Word (Word8, Word32)
import GHC.TypeLits (Symbol, symbolVal, KnownSymbol)
import Numeric (readFloat)
#ifdef USE_BINARY
import qualified PostgreSQL.Binary.Decoder as BinD
import qualified PostgreSQL.Binary.Encoder as BinE
#endif
type PGTextValue = BS.ByteString
type PGBinaryValue = BS.ByteString
data PGValue
= PGNullValue
| PGTextValue { pgTextValue :: PGTextValue }
| PGBinaryValue { pgBinaryValue :: PGBinaryValue }
deriving (Show, Eq)
type PGValues = [PGValue]
data PGTypeEnv = PGTypeEnv
{ pgIntegerDatetimes :: Maybe Bool
} deriving (Show)
unknownPGTypeEnv :: PGTypeEnv
unknownPGTypeEnv = PGTypeEnv
{ pgIntegerDatetimes = Nothing
}
data PGTypeName (t :: Symbol) = PGTypeProxy
class KnownSymbol t => PGType t where
pgTypeName :: PGTypeName t -> String
pgTypeName = symbolVal
pgBinaryColumn :: PGTypeEnv -> PGTypeName t -> Bool
pgBinaryColumn _ _ = False
class PGType t => PGParameter t a where
pgEncode :: PGTypeName t -> a -> PGTextValue
pgLiteral :: PGTypeName t -> a -> BS.ByteString
pgLiteral t = pgQuote . pgEncode t
pgEncodeValue :: PGTypeEnv -> PGTypeName t -> a -> PGValue
pgEncodeValue _ t = PGTextValue . pgEncode t
class PGType t => PGColumn t a where
pgDecode :: PGTypeName t -> PGTextValue -> a
pgDecodeBinary :: PGTypeEnv -> PGTypeName t -> PGBinaryValue -> a
pgDecodeBinary _ t _ = error $ "pgDecodeBinary " ++ pgTypeName t ++ ": not supported"
pgDecodeValue :: PGTypeEnv -> PGTypeName t -> PGValue -> a
pgDecodeValue _ t (PGTextValue v) = pgDecode t v
pgDecodeValue e t (PGBinaryValue v) = pgDecodeBinary e t v
pgDecodeValue _ t PGNullValue = error $ "NULL in " ++ pgTypeName t ++ " column (use Maybe or COALESCE)"
instance PGParameter t a => PGParameter t (Maybe a) where
pgEncode t = maybe (error $ "pgEncode " ++ pgTypeName t ++ ": Nothing") (pgEncode t)
pgLiteral = maybe (BSC.pack "NULL") . pgLiteral
pgEncodeValue e = maybe PGNullValue . pgEncodeValue e
instance PGColumn t a => PGColumn t (Maybe a) where
pgDecode t = Just . pgDecode t
pgDecodeBinary e t = Just . pgDecodeBinary e t
pgDecodeValue _ _ PGNullValue = Nothing
pgDecodeValue e t v = Just $ pgDecodeValue e t v
pgEncodeParameter :: PGParameter t a => PGTypeEnv -> PGTypeName t -> a -> PGValue
pgEncodeParameter = pgEncodeValue
pgEscapeParameter :: PGParameter t a => PGTypeEnv -> PGTypeName t -> a -> BS.ByteString
pgEscapeParameter _ = pgLiteral
pgDecodeColumn :: PGColumn t (Maybe a) => PGTypeEnv -> PGTypeName t -> PGValue -> Maybe a
pgDecodeColumn = pgDecodeValue
pgDecodeColumnNotNull :: PGColumn t a => PGTypeEnv -> PGTypeName t -> PGValue -> a
pgDecodeColumnNotNull = pgDecodeValue
pgQuoteUnsafe :: BS.ByteString -> BS.ByteString
pgQuoteUnsafe = (`BSC.snoc` '\'') . BSC.cons '\''
pgQuote :: BS.ByteString -> BS.ByteString
pgQuote = pgQuoteUnsafe . BSC.intercalate (BSC.pack "''") . BSC.split '\''
buildPGValue :: BSB.Builder -> BS.ByteString
buildPGValue = BSL.toStrict . BSB.toLazyByteString
pgDQuote :: [Char] -> BS.ByteString -> BSB.Builder
pgDQuote unsafe s
| BS.null s || BSC.any (\c -> isSpace c || c == '"' || c == '\\' || c `elem` unsafe) s || BSC.map toLower s == BSC.pack "null" =
dq <> BSBP.primMapByteStringBounded ec s <> dq
| otherwise = BSB.byteString s where
dq = BSB.char7 '"'
ec = BSBP.condB (\c -> c == c2w '"' || c == c2w '\\') bs (BSBP.liftFixedToBounded BSBP.word8)
bs = BSBP.liftFixedToBounded $ ((,) '\\') BSBP.>$< (BSBP.char7 BSBP.>*< BSBP.word8)
parsePGDQuote :: Bool -> [Char] -> (BS.ByteString -> Bool) -> P.Parser (Maybe BS.ByteString)
parsePGDQuote blank unsafe isnul = (Just <$> q) <> (mnul <$> uq) where
q = P.char '"' *> (BS.concat <$> qs)
qs = do
p <- P.takeTill (\c -> c == '"' || c == '\\')
e <- P.anyChar
if e == '"'
then return [p]
else do
c <- P.anyWord8
(p :) . (BS.singleton c :) <$> qs
uq = (if blank then P.takeWhile else P.takeWhile1) (`notElem` ('"':'\\':unsafe))
mnul s
| isnul s = Nothing
| otherwise = Just s
#ifdef USE_BINARY
binDec :: PGType t => BinD.Decoder a -> PGTypeName t -> PGBinaryValue -> a
binDec d t = either (\e -> error $ "pgDecodeBinary " ++ pgTypeName t ++ ": " ++ show e) id . BinD.run d
#define BIN_COL pgBinaryColumn _ _ = True
#define BIN_ENC(F) pgEncodeValue _ _ = PGBinaryValue . buildPGValue . F
#define BIN_DEC(F) pgDecodeBinary _ = F
#else
#define BIN_COL
#define BIN_ENC(F)
#define BIN_DEC(F)
#endif
instance PGType "void"
instance PGColumn "void" () where
pgDecode _ _ = ()
pgDecodeBinary _ _ _ = ()
pgDecodeValue _ _ _ = ()
instance PGType "boolean" where BIN_COL
instance PGParameter "boolean" Bool where
pgEncode _ False = BSC.singleton 'f'
pgEncode _ True = BSC.singleton 't'
pgLiteral _ False = BSC.pack "false"
pgLiteral _ True = BSC.pack "true"
BIN_ENC(BinE.bool)
instance PGColumn "boolean" Bool where
pgDecode _ s = case BSC.head s of
'f' -> False
't' -> True
c -> error $ "pgDecode boolean: " ++ [c]
BIN_DEC(binDec BinD.bool)
type OID = Word32
instance PGType "oid" where BIN_COL
instance PGParameter "oid" OID where
pgEncode _ = BSC.pack . show
pgLiteral = pgEncode
BIN_ENC(BinE.int4_word32)
instance PGColumn "oid" OID where
pgDecode _ = read . BSC.unpack
BIN_DEC(binDec BinD.int)
instance PGType "smallint" where BIN_COL
instance PGParameter "smallint" Int16 where
pgEncode _ = BSC.pack . show
pgLiteral = pgEncode
BIN_ENC(BinE.int2_int16)
instance PGColumn "smallint" Int16 where
pgDecode _ = read . BSC.unpack
BIN_DEC(binDec BinD.int)
instance PGType "integer" where BIN_COL
instance PGParameter "integer" Int32 where
pgEncode _ = BSC.pack . show
pgLiteral = pgEncode
BIN_ENC(BinE.int4_int32)
instance PGColumn "integer" Int32 where
pgDecode _ = read . BSC.unpack
BIN_DEC(binDec BinD.int)
instance PGType "bigint" where BIN_COL
instance PGParameter "bigint" Int64 where
pgEncode _ = BSC.pack . show
pgLiteral = pgEncode
BIN_ENC(BinE.int8_int64)
instance PGColumn "bigint" Int64 where
pgDecode _ = read . BSC.unpack
BIN_DEC(binDec BinD.int)
instance PGType "real" where BIN_COL
instance PGParameter "real" Float where
pgEncode _ = BSC.pack . show
pgLiteral = pgEncode
BIN_ENC(BinE.float4)
instance PGColumn "real" Float where
pgDecode _ = read . BSC.unpack
BIN_DEC(binDec BinD.float4)
instance PGType "double precision" where BIN_COL
instance PGParameter "double precision" Double where
pgEncode _ = BSC.pack . show
pgLiteral = pgEncode
BIN_ENC(BinE.float8)
instance PGColumn "double precision" Double where
pgDecode _ = read . BSC.unpack
BIN_DEC(binDec BinD.float8)
instance PGType "\"char\"" where BIN_COL
instance PGParameter "\"char\"" Char where
pgEncode _ = BSC.singleton
BIN_ENC(BinE.char)
instance PGColumn "\"char\"" Char where
pgDecode _ = BSC.head
BIN_DEC(binDec BinD.char)
class PGType t => PGStringType t
instance PGStringType t => PGParameter t String where
pgEncode _ = BSU.fromString
BIN_ENC(BinE.text_strict . T.pack)
instance PGStringType t => PGColumn t String where
pgDecode _ = BSU.toString
BIN_DEC((T.unpack .) . binDec BinD.text_strict)
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGStringType t => PGParameter t BS.ByteString where
pgEncode _ = id
BIN_ENC(BinE.text_strict . TE.decodeUtf8)
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGStringType t => PGColumn t BS.ByteString where
pgDecode _ = id
BIN_DEC((TE.encodeUtf8 .) . binDec BinD.text_strict)
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGStringType t => PGParameter t BSL.ByteString where
pgEncode _ = BSL.toStrict
BIN_ENC(BinE.text_lazy . TLE.decodeUtf8)
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGStringType t => PGColumn t BSL.ByteString where
pgDecode _ = BSL.fromStrict
BIN_DEC((TLE.encodeUtf8 .) . binDec BinD.text_lazy)
#ifdef USE_TEXT
instance PGStringType t => PGParameter t T.Text where
pgEncode _ = TE.encodeUtf8
BIN_ENC(BinE.text_strict)
instance PGStringType t => PGColumn t T.Text where
pgDecode _ = TE.decodeUtf8
BIN_DEC(binDec BinD.text_strict)
instance PGStringType t => PGParameter t TL.Text where
pgEncode _ = BSL.toStrict . TLE.encodeUtf8
BIN_ENC(BinE.text_lazy)
instance PGStringType t => PGColumn t TL.Text where
pgDecode _ = TL.fromStrict . TE.decodeUtf8
BIN_DEC(binDec BinD.text_lazy)
#endif
instance PGType "text" where BIN_COL
instance PGType "character varying" where BIN_COL
instance PGType "name" where BIN_COL
instance PGType "bpchar" where BIN_COL
instance PGStringType "text"
instance PGStringType "character varying"
instance PGStringType "name"
instance PGStringType "bpchar"
encodeBytea :: BSB.Builder -> PGTextValue
encodeBytea h = buildPGValue $ BSB.string7 "\\x" <> h
decodeBytea :: PGTextValue -> [Word8]
decodeBytea s
| sm /= "\\x" = error $ "pgDecode bytea: " ++ sm
| otherwise = pd $ BS.unpack d where
(m, d) = BS.splitAt 2 s
sm = BSC.unpack m
pd [] = []
pd (h:l:r) = (shiftL (unhex h) 4 .|. unhex l) : pd r
pd [x] = error $ "pgDecode bytea: " ++ show x
unhex = fromIntegral . digitToInt . w2c
instance PGType "bytea" where BIN_COL
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGParameter "bytea" BSL.ByteString where
pgEncode _ = encodeBytea . BSB.lazyByteStringHex
pgLiteral t = pgQuoteUnsafe . pgEncode t
BIN_ENC(BinE.bytea_lazy)
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGColumn "bytea" BSL.ByteString where
pgDecode _ = BSL.pack . decodeBytea
BIN_DEC(binDec BinD.bytea_lazy)
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGParameter "bytea" BS.ByteString where
pgEncode _ = encodeBytea . BSB.byteStringHex
pgLiteral t = pgQuoteUnsafe . pgEncode t
BIN_ENC(BinE.bytea_strict)
instance
#if __GLASGOW_HASKELL__ >= 710
#endif
PGColumn "bytea" BS.ByteString where
pgDecode _ = BS.pack . decodeBytea
BIN_DEC(binDec BinD.bytea_strict)
readTime :: Time.ParseTime t => String -> String -> t
readTime =
#if MIN_VERSION_time(1,5,0)
Time.parseTimeOrError False
#else
Time.readTime
#endif
defaultTimeLocale
instance PGType "date" where BIN_COL
instance PGParameter "date" Time.Day where
pgEncode _ = BSC.pack . Time.showGregorian
pgLiteral t = pgQuoteUnsafe . pgEncode t
BIN_ENC(BinE.date)
instance PGColumn "date" Time.Day where
pgDecode _ = readTime "%F" . BSC.unpack
BIN_DEC(binDec BinD.date)
binColDatetime :: PGTypeEnv -> PGTypeName t -> Bool
#ifdef USE_BINARY
binColDatetime PGTypeEnv{ pgIntegerDatetimes = Just _ } _ = True
#endif
binColDatetime _ _ = False
#ifdef USE_BINARY
binEncDatetime :: PGParameter t a => BinE.Encoder a -> BinE.Encoder a -> PGTypeEnv -> PGTypeName t -> a -> PGValue
binEncDatetime _ ff PGTypeEnv{ pgIntegerDatetimes = Just False } _ = PGBinaryValue . buildPGValue . ff
binEncDatetime fi _ PGTypeEnv{ pgIntegerDatetimes = Just True } _ = PGBinaryValue . buildPGValue . fi
binEncDatetime _ _ PGTypeEnv{ pgIntegerDatetimes = Nothing } t = PGTextValue . pgEncode t
binDecDatetime :: PGColumn t a => BinD.Decoder a -> BinD.Decoder a -> PGTypeEnv -> PGTypeName t -> PGBinaryValue -> a
binDecDatetime _ ff PGTypeEnv{ pgIntegerDatetimes = Just False } = binDec ff
binDecDatetime fi _ PGTypeEnv{ pgIntegerDatetimes = Just True } = binDec fi
binDecDatetime _ _ PGTypeEnv{ pgIntegerDatetimes = Nothing } = error "pgDecodeBinary: unknown integer_datetimes value"
#endif
instance PGType "time without time zone" where
pgBinaryColumn = binColDatetime
instance PGParameter "time without time zone" Time.TimeOfDay where
pgEncode _ = BSC.pack . Time.formatTime defaultTimeLocale "%T%Q"
pgLiteral t = pgQuoteUnsafe . pgEncode t
#ifdef USE_BINARY
pgEncodeValue = binEncDatetime BinE.time_int BinE.time_float
#endif
instance PGColumn "time without time zone" Time.TimeOfDay where
pgDecode _ = readTime "%T%Q" . BSC.unpack
#ifdef USE_BINARY
pgDecodeBinary = binDecDatetime BinD.time_int BinD.time_float
#endif
instance PGType "timestamp without time zone" where
pgBinaryColumn = binColDatetime
instance PGParameter "timestamp without time zone" Time.LocalTime where
pgEncode _ = BSC.pack . Time.formatTime defaultTimeLocale "%F %T%Q"
pgLiteral t = pgQuoteUnsafe . pgEncode t
#ifdef USE_BINARY
pgEncodeValue = binEncDatetime BinE.timestamp_int BinE.timestamp_float
#endif
instance PGColumn "timestamp without time zone" Time.LocalTime where
pgDecode _ = readTime "%F %T%Q" . BSC.unpack
#ifdef USE_BINARY
pgDecodeBinary = binDecDatetime BinD.timestamp_int BinD.timestamp_float
#endif
fixTZ :: String -> String
fixTZ "" = ""
fixTZ ['+',h1,h2] | isDigit h1 && isDigit h2 = ['+',h1,h2,':','0','0']
fixTZ ['-',h1,h2] | isDigit h1 && isDigit h2 = ['-',h1,h2,':','0','0']
fixTZ ['+',h1,h2,m1,m2] | isDigit h1 && isDigit h2 && isDigit m1 && isDigit m2 = ['+',h1,h2,':',m1,m2]
fixTZ ['-',h1,h2,m1,m2] | isDigit h1 && isDigit h2 && isDigit m1 && isDigit m2 = ['-',h1,h2,':',m1,m2]
fixTZ (c:s) = c:fixTZ s
instance PGType "timestamp with time zone" where
pgBinaryColumn = binColDatetime
instance PGParameter "timestamp with time zone" Time.UTCTime where
pgEncode _ = BSC.pack . fixTZ . Time.formatTime defaultTimeLocale "%F %T%Q%z"
#ifdef USE_BINARY
pgEncodeValue = binEncDatetime BinE.timestamptz_int BinE.timestamptz_float
#endif
instance PGColumn "timestamp with time zone" Time.UTCTime where
pgDecode _ = readTime "%F %T%Q%z" . fixTZ . BSC.unpack
#ifdef USE_BINARY
pgDecodeBinary = binDecDatetime BinD.timestamptz_int BinD.timestamptz_float
#endif
instance PGType "interval" where
pgBinaryColumn = binColDatetime
instance PGParameter "interval" Time.DiffTime where
pgEncode _ = BSC.pack . show
pgLiteral t = pgQuoteUnsafe . pgEncode t
#ifdef USE_BINARY
pgEncodeValue = binEncDatetime BinE.interval_int BinE.interval_float
#endif
instance PGColumn "interval" Time.DiffTime where
pgDecode _ a = either (error . ("pgDecode interval (" ++) . (++ ("): " ++ BSC.unpack a))) realToFrac $ P.parseOnly ps a where
ps = do
_ <- P.char 'P'
d <- units [('Y', 12*month), ('M', month), ('W', 7*day), ('D', day)]
((d +) <$> pt) <> (d <$ P.endOfInput)
pt = do
_ <- P.char 'T'
t <- units [('H', 3600), ('M', 60), ('S', 1)]
P.endOfInput
return t
units l = fmap sum $ P.many' $ do
x <- P.signed P.scientific
u <- P.choice $ map (\(c, u) -> u <$ P.char c) l
return $ x * u
day = 86400
month = 2629746
#ifdef USE_BINARY
pgDecodeBinary = binDecDatetime BinD.interval_int BinD.interval_float
#endif
instance PGType "numeric" where BIN_COL
instance PGParameter "numeric" Rational where
pgEncode _ r
| denominator r == 0 = BSC.pack "NaN"
| otherwise = BSC.pack $ take 30 (showRational (r / (10 ^^ e))) ++ 'e' : show e where
e = floor $ logBase (10 :: Double) $ fromRational $ abs r :: Int
pgLiteral _ r
| denominator r == 0 = BSC.pack "'NaN'"
| otherwise = BSC.pack $ '(' : show (numerator r) ++ '/' : show (denominator r) ++ "::numeric)"
BIN_ENC(BinE.numeric . realToFrac)
instance PGColumn "numeric" Rational where
pgDecode _ bs
| s == "NaN" = 0 % 0
| otherwise = ur $ readFloat s where
ur [(x,"")] = x
ur _ = error $ "pgDecode numeric: " ++ s
s = BSC.unpack bs
BIN_DEC((realToFrac .) . binDec BinD.numeric)
showRational :: Rational -> String
showRational r = show (ri :: Integer) ++ '.' : frac (abs rf) where
(ri, rf) = properFraction r
frac 0 = ""
frac f = intToDigit i : frac f' where (i, f') = properFraction (10 * f)
#ifdef USE_SCIENTIFIC
instance PGParameter "numeric" Scientific where
pgEncode _ = BSC.pack . show
pgLiteral = pgEncode
BIN_ENC(BinE.numeric)
instance PGColumn "numeric" Scientific where
pgDecode _ = read . BSC.unpack
BIN_DEC(binDec BinD.numeric)
#endif
#ifdef USE_UUID
instance PGType "uuid" where BIN_COL
instance PGParameter "uuid" UUID.UUID where
pgEncode _ = UUID.toASCIIBytes
pgLiteral t = pgQuoteUnsafe . pgEncode t
BIN_ENC(BinE.uuid)
instance PGColumn "uuid" UUID.UUID where
pgDecode _ u = fromMaybe (error $ "pgDecode uuid: " ++ BSC.unpack u) $ UUID.fromASCIIBytes u
BIN_DEC(binDec BinD.uuid)
#endif
newtype PGRecord = PGRecord [Maybe PGTextValue]
class PGType t => PGRecordType t
instance PGRecordType t => PGParameter t PGRecord where
pgEncode _ (PGRecord l) =
buildPGValue $ BSB.char7 '(' <> mconcat (intersperse (BSB.char7 ',') $ map (maybe mempty (pgDQuote "(),")) l) <> BSB.char7 ')'
pgLiteral _ (PGRecord l) =
BSC.pack "ROW(" <> BS.intercalate (BSC.singleton ',') (map (maybe (BSC.pack "NULL") pgQuote) l) `BSC.snoc` ')'
instance PGRecordType t => PGColumn t PGRecord where
pgDecode _ a = either (error . ("pgDecode record (" ++) . (++ ("): " ++ BSC.unpack a))) PGRecord $ P.parseOnly pa a where
pa = P.char '(' *> P.sepBy el (P.char ',') <* P.char ')' <* P.endOfInput
el = parsePGDQuote True "()," BS.null
instance PGType "record"
instance PGRecordType "record"
#ifdef USE_AESON
instance PGType "json"
instance PGParameter "json" JSON.Value where
pgEncode _ = BSL.toStrict . JSON.encode
instance PGColumn "json" JSON.Value where
pgDecode _ j = either (error . ("pgDecode json (" ++) . (++ ("): " ++ BSC.unpack j))) id $ P.parseOnly JSON.json j
instance PGType "jsonb"
instance PGParameter "jsonb" JSON.Value where
pgEncode _ = BSL.toStrict . JSON.encode
instance PGColumn "jsonb" JSON.Value where
pgDecode _ j = either (error . ("pgDecode jsonb (" ++) . (++ ("): " ++ BSC.unpack j))) id $ P.parseOnly JSON.json j
#endif