Home | History | Annotate | Download | only in json
      1 // 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 
      5 package json
      6 
      7 import "bytes"
      8 
      9 // Compact appends to dst the JSON-encoded src with
     10 // insignificant space characters elided.
     11 func Compact(dst *bytes.Buffer, src []byte) error {
     12 	return compact(dst, src, false)
     13 }
     14 
     15 func 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 
     60 func 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.
     79 func 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 }
    142