module Data.Aeson.Encode
(
fromValue
, encode
) where
import Data.Aeson.Types (ToJSON(..), Value(..))
import Data.Attoparsec.Number (Number(..))
import Data.Monoid (mappend)
import Data.Text.Lazy.Builder
import Data.Text.Lazy.Builder.Int (decimal)
import Data.Text.Lazy.Builder.RealFloat (realFloat)
import Data.Text.Lazy.Encoding (encodeUtf8)
import Numeric (showHex)
import qualified Data.ByteString.Lazy as L
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import qualified Data.Vector as V
fromValue :: Value -> Builder
fromValue Null = "null"
fromValue (Bool b) =
if b then "true" else "false"
fromValue (Number n) = fromNumber n
fromValue (String s) = string s
fromValue (Array v)
| V.null v = "[]"
| otherwise =
singleton '[' <>
fromValue (V.unsafeHead v) <>
V.foldr f (singleton ']') (V.unsafeTail v)
where f a z = singleton ',' <> fromValue a <> z
fromValue (Object m) =
case H.toList m of
(x:xs) -> singleton '{' <> one x <> foldr f (singleton '}') xs
_ -> "{}"
where f a z = singleton ',' <> one a <> z
one (k,v) = string k <> singleton ':' <> fromValue v
string :: T.Text -> Builder
string s = singleton '"' <> quote s <> singleton '"'
where
quote q = case T.uncons t of
Nothing -> fromText h
Just (!c,t') -> fromText h <> escape c <> quote t'
where (h,t) = T.break isEscape q
isEscape c = c == '\"' ||
c == '\\' ||
c == '<' ||
c == '>' ||
c < '\x20'
escape '\"' = "\\\""
escape '\\' = "\\\\"
escape '\n' = "\\n"
escape '\r' = "\\r"
escape '\t' = "\\t"
escape '<' = "\\u003c"
escape '>' = "\\u003e"
escape c
| c < '\x20' = fromString $ "\\u" ++ replicate (4 length h) '0' ++ h
| otherwise = singleton c
where h = showHex (fromEnum c) ""
fromNumber :: Number -> Builder
fromNumber (I i) = decimal i
fromNumber (D d)
| isNaN d || isInfinite d = "null"
| otherwise = realFloat d
encode :: ToJSON a => a -> L.ByteString
encode = encodeUtf8 . toLazyText . fromValue .
toJSON
(<>) :: Builder -> Builder -> Builder
(<>) = mappend
infixr 6 <>