blob: fba19548c92721d2f2fb0c63dd3accaa8a3077d3 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package json
6
7import "bytes"
8
9// Compact appends to dst the JSON-encoded src with
10// insignificant space characters elided.
11func Compact(dst *bytes.Buffer, src []byte) error {
12 return compact(dst, src, false)
13}
14
15func compact(dst *bytes.Buffer, src []byte, escape bool) error {
16 origLen := dst.Len()
17 var scan scanner
18 scan.reset()
19 start := 0
20 for i, c := range src {
21 if escape && (c == '<' || c == '>' || c == '&') {
22 if start < i {
23 dst.Write(src[start:i])
24 }
25 dst.WriteString(`\u00`)
26 dst.WriteByte(hex[c>>4])
27 dst.WriteByte(hex[c&0xF])
28 start = i + 1
29 }
30 // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
31 if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
32 if start < i {
33 dst.Write(src[start:i])
34 }
35 dst.WriteString(`\u202`)
36 dst.WriteByte(hex[src[i+2]&0xF])
37 start = i + 3
38 }
39 v := scan.step(&scan, c)
40 if v >= scanSkipSpace {
41 if v == scanError {
42 break
43 }
44 if start < i {
45 dst.Write(src[start:i])
46 }
47 start = i + 1
48 }
49 }
50 if scan.eof() == scanError {
51 dst.Truncate(origLen)
52 return scan.err
53 }
54 if start < len(src) {
55 dst.Write(src[start:])
56 }
57 return nil
58}
59
60func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
61 dst.WriteByte('\n')
62 dst.WriteString(prefix)
63 for i := 0; i < depth; i++ {
64 dst.WriteString(indent)
65 }
66}
67
68// Indent appends to dst an indented form of the JSON-encoded src.
69// Each element in a JSON object or array begins on a new,
70// indented line beginning with prefix followed by one or more
71// copies of indent according to the indentation nesting.
72// The data appended to dst does not begin with the prefix nor
73// any indentation, to make it easier to embed inside other formatted JSON data.
74// Although leading space characters (space, tab, carriage return, newline)
75// at the beginning of src are dropped, trailing space characters
76// at the end of src are preserved and copied to dst.
77// For example, if src has no trailing spaces, neither will dst;
78// if src ends in a trailing newline, so will dst.
79func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
80 origLen := dst.Len()
81 var scan scanner
82 scan.reset()
83 needIndent := false
84 depth := 0
85 for _, c := range src {
86 scan.bytes++
87 v := scan.step(&scan, c)
88 if v == scanSkipSpace {
89 continue
90 }
91 if v == scanError {
92 break
93 }
94 if needIndent && v != scanEndObject && v != scanEndArray {
95 needIndent = false
96 depth++
97 newline(dst, prefix, indent, depth)
98 }
99
100 // Emit semantically uninteresting bytes
101 // (in particular, punctuation in strings) unmodified.
102 if v == scanContinue {
103 dst.WriteByte(c)
104 continue
105 }
106
107 // Add spacing around real punctuation.
108 switch c {
109 case '{', '[':
110 // delay indent so that empty object and array are formatted as {} and [].
111 needIndent = true
112 dst.WriteByte(c)
113
114 case ',':
115 dst.WriteByte(c)
116 newline(dst, prefix, indent, depth)
117
118 case ':':
119 dst.WriteByte(c)
120 dst.WriteByte(' ')
121
122 case '}', ']':
123 if needIndent {
124 // suppress indent in empty object/array
125 needIndent = false
126 } else {
127 depth--
128 newline(dst, prefix, indent, depth)
129 }
130 dst.WriteByte(c)
131
132 default:
133 dst.WriteByte(c)
134 }
135 }
136 if scan.eof() == scanError {
137 dst.Truncate(origLen)
138 return scan.err
139 }
140 return nil
141}