Blame Data/Aeson/Text.hs

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)