indent.gno
2.73 Kb ยท 130 lines
1package json
2
3import (
4 "bytes"
5 "strings"
6)
7
8// indentGrowthFactor specifies the growth factor of indenting JSON input.
9// A factor no higher than 2 ensures that wasted space never exceeds 50%.
10const indentGrowthFactor = 2
11
12// IndentJSON formats the JSON data with the specified indentation.
13func Indent(data []byte, indent string) ([]byte, error) {
14 var (
15 out bytes.Buffer
16 level int
17 inArray bool
18 arrayDepth int
19 )
20
21 for i := 0; i < len(data); i++ {
22 c := data[i] // current character
23
24 switch c {
25 case bracketOpen:
26 arrayDepth++
27 if arrayDepth > 1 {
28 level++ // increase the level if it's nested array
29 inArray = true
30
31 if err := out.WriteByte(c); err != nil {
32 return nil, err
33 }
34
35 if err := writeNewlineAndIndent(&out, level, indent); err != nil {
36 return nil, err
37 }
38 } else {
39 // case of the top-level array
40 inArray = true
41 if err := out.WriteByte(c); err != nil {
42 return nil, err
43 }
44 }
45
46 case bracketClose:
47 if inArray && arrayDepth > 1 { // nested array
48 level--
49 if err := writeNewlineAndIndent(&out, level, indent); err != nil {
50 return nil, err
51 }
52 }
53
54 arrayDepth--
55 if arrayDepth == 0 {
56 inArray = false
57 }
58
59 if err := out.WriteByte(c); err != nil {
60 return nil, err
61 }
62
63 case curlyOpen:
64 // check if the empty object or array
65 // we don't need to apply the indent when it's empty containers.
66 if i+1 < len(data) && data[i+1] == curlyClose {
67 if err := out.WriteByte(c); err != nil {
68 return nil, err
69 }
70
71 i++ // skip next character
72 if err := out.WriteByte(data[i]); err != nil {
73 return nil, err
74 }
75 } else {
76 if err := out.WriteByte(c); err != nil {
77 return nil, err
78 }
79
80 level++
81 if err := writeNewlineAndIndent(&out, level, indent); err != nil {
82 return nil, err
83 }
84 }
85
86 case curlyClose:
87 level--
88 if err := writeNewlineAndIndent(&out, level, indent); err != nil {
89 return nil, err
90 }
91 if err := out.WriteByte(c); err != nil {
92 return nil, err
93 }
94
95 case comma, colon:
96 if err := out.WriteByte(c); err != nil {
97 return nil, err
98 }
99 if inArray && arrayDepth > 1 { // nested array
100 if err := writeNewlineAndIndent(&out, level, indent); err != nil {
101 return nil, err
102 }
103 } else if c == colon {
104 if err := out.WriteByte(' '); err != nil {
105 return nil, err
106 }
107 }
108
109 default:
110 if err := out.WriteByte(c); err != nil {
111 return nil, err
112 }
113 }
114 }
115
116 return out.Bytes(), nil
117}
118
119func writeNewlineAndIndent(out *bytes.Buffer, level int, indent string) error {
120 if err := out.WriteByte('\n'); err != nil {
121 return err
122 }
123
124 idt := strings.Repeat(indent, level*indentGrowthFactor)
125 if _, err := out.WriteString(idt); err != nil {
126 return err
127 }
128
129 return nil
130}