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}