|
Packit |
9a2dfb |
{-# LANGUAGE BangPatterns #-}
|
|
Packit |
9a2dfb |
{-# LANGUAGE OverloadedStrings #-}
|
|
Packit |
9a2dfb |
-- |
|
|
Packit |
9a2dfb |
-- Module: Data.Aeson.Text
|
|
Packit |
9a2dfb |
-- Copyright: (c) 2012-2016 Bryan O'Sullivan
|
|
Packit |
9a2dfb |
-- (c) 2011 MailRank, Inc.
|
|
Packit |
9a2dfb |
-- License: BSD3
|
|
Packit |
9a2dfb |
-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
|
|
Packit |
9a2dfb |
-- Stability: experimental
|
|
Packit |
9a2dfb |
-- Portability: portable
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Most frequently, you'll probably want to encode straight to UTF-8
|
|
Packit |
9a2dfb |
-- (the standard JSON encoding) using 'encode'.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- You can use the conversions to 'Builder's when embedding JSON messages as
|
|
Packit |
9a2dfb |
-- parts of a protocol.
|
|
Packit |
9a2dfb |
module Data.Aeson.Text
|
|
Packit |
9a2dfb |
(
|
|
Packit |
9a2dfb |
encodeToLazyText
|
|
Packit |
9a2dfb |
, encodeToTextBuilder
|
|
Packit |
9a2dfb |
) where
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
import Prelude ()
|
|
Packit |
9a2dfb |
import Prelude.Compat
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
import Data.Aeson.Types (Value(..), ToJSON(..))
|
|
Packit |
9a2dfb |
import Data.Aeson.Encoding (encodingToLazyByteString)
|
|
Packit |
9a2dfb |
import Data.Monoid ((<>))
|
|
Packit |
9a2dfb |
import Data.Scientific (FPFormat(..), Scientific, base10Exponent)
|
|
Packit |
9a2dfb |
import Data.Text.Lazy.Builder
|
|
Packit |
9a2dfb |
import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
|
|
Packit |
9a2dfb |
import Numeric (showHex)
|
|
Packit |
9a2dfb |
import qualified Data.HashMap.Strict as H
|
|
Packit |
9a2dfb |
import qualified Data.Text as T
|
|
Packit |
9a2dfb |
import qualified Data.Text.Lazy as LT
|
|
Packit |
9a2dfb |
import qualified Data.Text.Lazy.Encoding as LT
|
|
Packit |
9a2dfb |
import qualified Data.Vector as V
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Encode a JSON 'Value' to a "Data.Text.Lazy"
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- /Note:/ uses 'toEncoding'
|
|
Packit |
9a2dfb |
encodeToLazyText :: ToJSON a => a -> LT.Text
|
|
Packit |
9a2dfb |
encodeToLazyText = LT.decodeUtf8 . encodingToLazyByteString . toEncoding
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Encode a JSON 'Value' to a "Data.Text" 'Builder', which can be
|
|
Packit |
9a2dfb |
-- embedded efficiently in a text-based protocol.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- If you are going to immediately encode straight to a
|
|
Packit |
9a2dfb |
-- 'L.ByteString', it is more efficient to use 'encodeToBuilder'
|
|
Packit |
9a2dfb |
-- instead.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- /Note:/ Uses 'toJSON'
|
|
Packit |
9a2dfb |
encodeToTextBuilder :: ToJSON a => a -> Builder
|
|
Packit |
9a2dfb |
encodeToTextBuilder =
|
|
Packit |
9a2dfb |
go . toJSON
|
|
Packit |
9a2dfb |
where
|
|
Packit |
9a2dfb |
go Null = {-# SCC "go/Null" #-} "null"
|
|
Packit |
9a2dfb |
go (Bool b) = {-# SCC "go/Bool" #-} if b then "true" else "false"
|
|
Packit |
9a2dfb |
go (Number s) = {-# SCC "go/Number" #-} fromScientific s
|
|
Packit |
9a2dfb |
go (String s) = {-# SCC "go/String" #-} string s
|
|
Packit |
9a2dfb |
go (Array v)
|
|
Packit |
9a2dfb |
| V.null v = {-# SCC "go/Array" #-} "[]"
|
|
Packit |
9a2dfb |
| otherwise = {-# SCC "go/Array" #-}
|
|
Packit |
9a2dfb |
singleton '[' <>
|
|
Packit |
9a2dfb |
go (V.unsafeHead v) <>
|
|
Packit |
9a2dfb |
V.foldr f (singleton ']') (V.unsafeTail v)
|
|
Packit |
9a2dfb |
where f a z = singleton ',' <> go a <> z
|
|
Packit |
9a2dfb |
go (Object m) = {-# SCC "go/Object" #-}
|
|
Packit |
9a2dfb |
case H.toList m of
|
|
Packit |
9a2dfb |
(x:xs) -> singleton '{' <> one x <> foldr f (singleton '}') xs
|
|
Packit |
9a2dfb |
_ -> "{}"
|
|
Packit |
9a2dfb |
where f a z = singleton ',' <> one a <> z
|
|
Packit |
9a2dfb |
one (k,v) = string k <> singleton ':' <> go v
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
string :: T.Text -> Builder
|
|
Packit |
9a2dfb |
string s = {-# SCC "string" #-} singleton '"' <> quote s <> singleton '"'
|
|
Packit |
9a2dfb |
where
|
|
Packit |
9a2dfb |
quote q = case T.uncons t of
|
|
Packit |
9a2dfb |
Nothing -> fromText h
|
|
Packit |
9a2dfb |
Just (!c,t') -> fromText h <> escape c <> quote t'
|
|
Packit |
9a2dfb |
where (h,t) = {-# SCC "break" #-} T.break isEscape q
|
|
Packit |
9a2dfb |
isEscape c = c == '\"' ||
|
|
Packit |
9a2dfb |
c == '\\' ||
|
|
Packit |
9a2dfb |
c < '\x20'
|
|
Packit |
9a2dfb |
escape '\"' = "\\\""
|
|
Packit |
9a2dfb |
escape '\\' = "\\\\"
|
|
Packit |
9a2dfb |
escape '\n' = "\\n"
|
|
Packit |
9a2dfb |
escape '\r' = "\\r"
|
|
Packit |
9a2dfb |
escape '\t' = "\\t"
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
escape c
|
|
Packit |
9a2dfb |
| c < '\x20' = fromString $ "\\u" ++ replicate (4 - length h) '0' ++ h
|
|
Packit |
9a2dfb |
| otherwise = singleton c
|
|
Packit |
9a2dfb |
where h = showHex (fromEnum c) ""
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
fromScientific :: Scientific -> Builder
|
|
Packit |
9a2dfb |
fromScientific s = formatScientificBuilder format prec s
|
|
Packit |
9a2dfb |
where
|
|
Packit |
9a2dfb |
(format, prec)
|
|
Packit |
9a2dfb |
| base10Exponent s < 0 = (Generic, Nothing)
|
|
Packit |
9a2dfb |
| otherwise = (Fixed, Just 0)
|