github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/p/demo/json/indent.gno (about) 1 package json 2 3 import ( 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%. 10 const indentGrowthFactor = 2 11 12 // IndentJSON takes a JSON byte slice and a string for indentation, 13 // then formats the JSON according to the specified indent string. 14 // This function applies indentation rules as follows: 15 // 16 // 1. For top-level arrays and objects, no additional indentation is applied. 17 // 18 // 2. For nested structures like arrays within arrays or objects, indentation increases. 19 // 20 // 3. Indentation is applied after opening brackets ('[' or '{') and before closing brackets (']' or '}'). 21 // 22 // 4. Commas and colons are handled appropriately to maintain valid JSON format. 23 // 24 // 5. Nested arrays within objects or arrays receive new lines and indentation based on their depth level. 25 // 26 // The function returns the formatted JSON as a byte slice and an error if any issues occurred during formatting. 27 func Indent(data []byte, indent string) ([]byte, error) { 28 var ( 29 out bytes.Buffer 30 level int 31 inArray bool 32 arrayDepth int 33 ) 34 35 for i := 0; i < len(data); i++ { 36 c := data[i] // current character 37 38 switch c { 39 case bracketOpen: 40 arrayDepth++ 41 if arrayDepth > 1 { 42 level++ // increase the level if it's nested array 43 inArray = true 44 45 if err := out.WriteByte(c); err != nil { 46 return nil, err 47 } 48 49 if err := writeNewlineAndIndent(&out, level, indent); err != nil { 50 return nil, err 51 } 52 } else { 53 // case of the top-level array 54 inArray = true 55 if err := out.WriteByte(c); err != nil { 56 return nil, err 57 } 58 } 59 60 case bracketClose: 61 if inArray && arrayDepth > 1 { // nested array 62 level-- 63 if err := writeNewlineAndIndent(&out, level, indent); err != nil { 64 return nil, err 65 } 66 } 67 68 arrayDepth-- 69 if arrayDepth == 0 { 70 inArray = false 71 } 72 73 if err := out.WriteByte(c); err != nil { 74 return nil, err 75 } 76 77 case curlyOpen: 78 // check if the empty object or array 79 // we don't need to apply the indent when it's empty containers. 80 if i+1 < len(data) && data[i+1] == curlyClose { 81 if err := out.WriteByte(c); err != nil { 82 return nil, err 83 } 84 85 i++ // skip next character 86 if err := out.WriteByte(data[i]); err != nil { 87 return nil, err 88 } 89 } else { 90 if err := out.WriteByte(c); err != nil { 91 return nil, err 92 } 93 94 level++ 95 if err := writeNewlineAndIndent(&out, level, indent); err != nil { 96 return nil, err 97 } 98 } 99 100 case curlyClose: 101 level-- 102 if err := writeNewlineAndIndent(&out, level, indent); err != nil { 103 return nil, err 104 } 105 if err := out.WriteByte(c); err != nil { 106 return nil, err 107 } 108 109 case comma, colon: 110 if err := out.WriteByte(c); err != nil { 111 return nil, err 112 } 113 if inArray && arrayDepth > 1 { // nested array 114 if err := writeNewlineAndIndent(&out, level, indent); err != nil { 115 return nil, err 116 } 117 } else if c == colon { 118 if err := out.WriteByte(' '); err != nil { 119 return nil, err 120 } 121 } 122 123 default: 124 if err := out.WriteByte(c); err != nil { 125 return nil, err 126 } 127 } 128 } 129 130 return out.Bytes(), nil 131 } 132 133 func writeNewlineAndIndent(out *bytes.Buffer, level int, indent string) error { 134 if err := out.WriteByte('\n'); err != nil { 135 return err 136 } 137 138 idt := strings.Repeat(indent, level*indentGrowthFactor) 139 if _, err := out.WriteString(idt); err != nil { 140 return err 141 } 142 143 return nil 144 }