175 lines
4.1 KiB
Go
175 lines
4.1 KiB
Go
// Copyright 2010 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package jsonx extends the encoding/json package to encode JSON
|
|
// incrementally and without requiring reflection.
|
|
package jsonx
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
var hex = "0123456789abcdef"
|
|
|
|
// AppendString escapes s appends it to buf.
|
|
func AppendString(buf *bytes.Buffer, s string) {
|
|
buf.WriteByte('"')
|
|
start := 0
|
|
for i := 0; i < len(s); {
|
|
if b := s[i]; b < utf8.RuneSelf {
|
|
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
|
i++
|
|
continue
|
|
}
|
|
if start < i {
|
|
buf.WriteString(s[start:i])
|
|
}
|
|
switch b {
|
|
case '\\', '"':
|
|
buf.WriteByte('\\')
|
|
buf.WriteByte(b)
|
|
case '\n':
|
|
buf.WriteByte('\\')
|
|
buf.WriteByte('n')
|
|
case '\r':
|
|
buf.WriteByte('\\')
|
|
buf.WriteByte('r')
|
|
case '\t':
|
|
buf.WriteByte('\\')
|
|
buf.WriteByte('t')
|
|
default:
|
|
// This encodes bytes < 0x20 except for \n and \r,
|
|
// as well as <, > and &. The latter are escaped because they
|
|
// can lead to security holes when user-controlled strings
|
|
// are rendered into JSON and served to some browsers.
|
|
buf.WriteString(`\u00`)
|
|
buf.WriteByte(hex[b>>4])
|
|
buf.WriteByte(hex[b&0xF])
|
|
}
|
|
i++
|
|
start = i
|
|
continue
|
|
}
|
|
c, size := utf8.DecodeRuneInString(s[i:])
|
|
if c == utf8.RuneError && size == 1 {
|
|
if start < i {
|
|
buf.WriteString(s[start:i])
|
|
}
|
|
buf.WriteString(`\ufffd`)
|
|
i += size
|
|
start = i
|
|
continue
|
|
}
|
|
// U+2028 is LINE SEPARATOR.
|
|
// U+2029 is PARAGRAPH SEPARATOR.
|
|
// They are both technically valid characters in JSON strings,
|
|
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
|
// and can lead to security holes there. It is valid JSON to
|
|
// escape them, so we do so unconditionally.
|
|
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
|
if c == '\u2028' || c == '\u2029' {
|
|
if start < i {
|
|
buf.WriteString(s[start:i])
|
|
}
|
|
buf.WriteString(`\u202`)
|
|
buf.WriteByte(hex[c&0xF])
|
|
i += size
|
|
start = i
|
|
continue
|
|
}
|
|
i += size
|
|
}
|
|
if start < len(s) {
|
|
buf.WriteString(s[start:])
|
|
}
|
|
buf.WriteByte('"')
|
|
}
|
|
|
|
// AppendStringArray appends an array of string literals to buf.
|
|
func AppendStringArray(buf *bytes.Buffer, a ...string) {
|
|
buf.WriteByte('[')
|
|
for i, s := range a {
|
|
if i > 0 {
|
|
buf.WriteByte(',')
|
|
}
|
|
AppendString(buf, s)
|
|
}
|
|
buf.WriteByte(']')
|
|
}
|
|
|
|
// AppendFloat appends a numeric literal representing the value to buf.
|
|
func AppendFloat(buf *bytes.Buffer, x float64) error {
|
|
var scratch [64]byte
|
|
|
|
if math.IsInf(x, 0) || math.IsNaN(x) {
|
|
return &json.UnsupportedValueError{
|
|
Value: reflect.ValueOf(x),
|
|
Str: strconv.FormatFloat(x, 'g', -1, 64),
|
|
}
|
|
}
|
|
|
|
buf.Write(strconv.AppendFloat(scratch[:0], x, 'g', -1, 64))
|
|
return nil
|
|
}
|
|
|
|
// AppendFloatArray appends an array of numeric literals to buf.
|
|
func AppendFloatArray(buf *bytes.Buffer, a ...float64) error {
|
|
buf.WriteByte('[')
|
|
for i, x := range a {
|
|
if i > 0 {
|
|
buf.WriteByte(',')
|
|
}
|
|
if err := AppendFloat(buf, x); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
buf.WriteByte(']')
|
|
return nil
|
|
}
|
|
|
|
// AppendInt appends a numeric literal representing the value to buf.
|
|
func AppendInt(buf *bytes.Buffer, x int64) {
|
|
var scratch [64]byte
|
|
buf.Write(strconv.AppendInt(scratch[:0], x, 10))
|
|
}
|
|
|
|
// AppendIntArray appends an array of numeric literals to buf.
|
|
func AppendIntArray(buf *bytes.Buffer, a ...int64) {
|
|
var scratch [64]byte
|
|
|
|
buf.WriteByte('[')
|
|
for i, x := range a {
|
|
if i > 0 {
|
|
buf.WriteByte(',')
|
|
}
|
|
buf.Write(strconv.AppendInt(scratch[:0], x, 10))
|
|
}
|
|
buf.WriteByte(']')
|
|
}
|
|
|
|
// AppendUint appends a numeric literal representing the value to buf.
|
|
func AppendUint(buf *bytes.Buffer, x uint64) {
|
|
var scratch [64]byte
|
|
buf.Write(strconv.AppendUint(scratch[:0], x, 10))
|
|
}
|
|
|
|
// AppendUintArray appends an array of numeric literals to buf.
|
|
func AppendUintArray(buf *bytes.Buffer, a ...uint64) {
|
|
var scratch [64]byte
|
|
|
|
buf.WriteByte('[')
|
|
for i, x := range a {
|
|
if i > 0 {
|
|
buf.WriteByte(',')
|
|
}
|
|
buf.Write(strconv.AppendUint(scratch[:0], x, 10))
|
|
}
|
|
buf.WriteByte(']')
|
|
}
|