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  }