1package json
2
3import "unicode/utf8"
4
5const hex = "0123456789abcdef"
6
7var noEscapeTable = [256]bool{}
8
9func init() {
10	for i := 0; i <= 0x7e; i++ {
11		noEscapeTable[i] = i >= 0x20 && i != '\\' && i != '"'
12	}
13}
14
15// AppendStrings encodes the input strings to json and
16// appends the encoded string list to the input byte slice.
17func (e Encoder) AppendStrings(dst []byte, vals []string) []byte {
18	if len(vals) == 0 {
19		return append(dst, '[', ']')
20	}
21	dst = append(dst, '[')
22	dst = e.AppendString(dst, vals[0])
23	if len(vals) > 1 {
24		for _, val := range vals[1:] {
25			dst = e.AppendString(append(dst, ','), val)
26		}
27	}
28	dst = append(dst, ']')
29	return dst
30}
31
32// AppendString encodes the input string to json and appends
33// the encoded string to the input byte slice.
34//
35// The operation loops though each byte in the string looking
36// for characters that need json or utf8 encoding. If the string
37// does not need encoding, then the string is appended in it's
38// entirety to the byte slice.
39// If we encounter a byte that does need encoding, switch up
40// the operation and perform a byte-by-byte read-encode-append.
41func (Encoder) AppendString(dst []byte, s string) []byte {
42	// Start with a double quote.
43	dst = append(dst, '"')
44	// Loop through each character in the string.
45	for i := 0; i < len(s); i++ {
46		// Check if the character needs encoding. Control characters, slashes,
47		// and the double quote need json encoding. Bytes above the ascii
48		// boundary needs utf8 encoding.
49		if !noEscapeTable[s[i]] {
50			// We encountered a character that needs to be encoded. Switch
51			// to complex version of the algorithm.
52			dst = appendStringComplex(dst, s, i)
53			return append(dst, '"')
54		}
55	}
56	// The string has no need for encoding an therefore is directly
57	// appended to the byte slice.
58	dst = append(dst, s...)
59	// End with a double quote
60	return append(dst, '"')
61}
62
63// appendStringComplex is used by appendString to take over an in
64// progress JSON string encoding that encountered a character that needs
65// to be encoded.
66func appendStringComplex(dst []byte, s string, i int) []byte {
67	start := 0
68	for i < len(s) {
69		b := s[i]
70		if b >= utf8.RuneSelf {
71			r, size := utf8.DecodeRuneInString(s[i:])
72			if r == utf8.RuneError && size == 1 {
73				// In case of error, first append previous simple characters to
74				// the byte slice if any and append a remplacement character code
75				// in place of the invalid sequence.
76				if start < i {
77					dst = append(dst, s[start:i]...)
78				}
79				dst = append(dst, `\ufffd`...)
80				i += size
81				start = i
82				continue
83			}
84			i += size
85			continue
86		}
87		if noEscapeTable[b] {
88			i++
89			continue
90		}
91		// We encountered a character that needs to be encoded.
92		// Let's append the previous simple characters to the byte slice
93		// and switch our operation to read and encode the remainder
94		// characters byte-by-byte.
95		if start < i {
96			dst = append(dst, s[start:i]...)
97		}
98		switch b {
99		case '"', '\\':
100			dst = append(dst, '\\', b)
101		case '\b':
102			dst = append(dst, '\\', 'b')
103		case '\f':
104			dst = append(dst, '\\', 'f')
105		case '\n':
106			dst = append(dst, '\\', 'n')
107		case '\r':
108			dst = append(dst, '\\', 'r')
109		case '\t':
110			dst = append(dst, '\\', 't')
111		default:
112			dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
113		}
114		i++
115		start = i
116	}
117	if start < len(s) {
118		dst = append(dst, s[start:]...)
119	}
120	return dst
121}
122