Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

json.gno

3.12 Kb · 123 lines
  1package safe
  2
  3import (
  4	"strconv"
  5	"strings"
  6)
  7
  8// JSONString returns s as a quoted JSON string. Beyond the mandatory
  9// escapes it also escapes U+2028/U+2029 (RA L-12) and '<', '>', '&', so the
 10// output is safe to inline in HTML as well.
 11func JSONString(s string) string {
 12	const hexDigits = "0123456789abcdef"
 13	var b strings.Builder
 14	b.WriteByte('"')
 15	for _, r := range s {
 16		switch r {
 17		case '"':
 18			b.WriteString(`\"`)
 19		case '\\':
 20			b.WriteString(`\\`)
 21		case '\n':
 22			b.WriteString(`\n`)
 23		case '\r':
 24			b.WriteString(`\r`)
 25		case '\t':
 26			b.WriteString(`\t`)
 27		case '<', '>', '&', 0x2028, 0x2029:
 28			b.WriteString(`\u`)
 29			b.WriteString(PadHex(int64(r), 4))
 30		default:
 31			if r < 0x20 || r == 0x7f {
 32				b.WriteString(`\u00`)
 33				b.WriteByte(hexDigits[(r>>4)&0xf])
 34				b.WriteByte(hexDigits[r&0xf])
 35				continue
 36			}
 37			b.WriteRune(r)
 38		}
 39	}
 40	b.WriteByte('"')
 41	return b.String()
 42}
 43
 44// PadHex renders n in lowercase hex, zero-padded to width.
 45func PadHex(n int64, width int) string {
 46	s := strconv.FormatInt(n, 16)
 47	if len(s) >= width {
 48		return s
 49	}
 50	return strings.Repeat("0", width-len(s)) + s
 51}
 52
 53// JSONWriter is the single JSON producer of every read API (design
 54// §2.5: reads return primitives or JSON, never positional text). It tracks
 55// commas itself, so callers only describe structure:
 56//
 57//	w := &safe.JSONWriter{}
 58//	w.BeginObject(); w.Key("id"); w.Str("1"); w.EndObject()
 59type JSONWriter struct {
 60	b        strings.Builder
 61	hasItems []bool // one entry per open container
 62	afterKey bool
 63}
 64
 65func (w *JSONWriter) BeginObject() { w.open('{') }
 66func (w *JSONWriter) EndObject()   { w.close('}') }
 67func (w *JSONWriter) BeginArray()  { w.open('[') }
 68func (w *JSONWriter) EndArray()    { w.close(']') }
 69
 70// Key starts an object member; the next value call writes its value.
 71func (w *JSONWriter) Key(k string) {
 72	w.separate()
 73	w.b.WriteString(JSONString(k))
 74	w.b.WriteByte(':')
 75	w.afterKey = true
 76}
 77
 78func (w *JSONWriter) Str(v string) { w.value(JSONString(v)) }
 79func (w *JSONWriter) Int(v int64)  { w.value(strconv.FormatInt(v, 10)) }
 80func (w *JSONWriter) Bool(v bool)  { w.value(strconv.FormatBool(v)) }
 81
 82// Raw writes v verbatim. Only for JSON produced by another JSONWriter.
 83func (w *JSONWriter) Raw(v string) { w.value(v) }
 84
 85// StrField and IntField are Key+value shorthands.
 86func (w *JSONWriter) StrField(k, v string) { w.Key(k); w.Str(v) }
 87func (w *JSONWriter) IntField(k string, v int64) { w.Key(k); w.Int(v) }
 88func (w *JSONWriter) BoolField(k string, v bool) { w.Key(k); w.Bool(v) }
 89
 90func (w *JSONWriter) String() string { return w.b.String() }
 91
 92func (w *JSONWriter) open(c byte) {
 93	w.separate()
 94	w.b.WriteByte(c)
 95	w.hasItems = append(w.hasItems, false)
 96}
 97
 98func (w *JSONWriter) close(c byte) {
 99	if len(w.hasItems) == 0 {
100		panic("JSONWriter: close without open")
101	}
102	w.hasItems = w.hasItems[:len(w.hasItems)-1]
103	w.b.WriteByte(c)
104}
105
106func (w *JSONWriter) value(v string) {
107	w.separate()
108	w.b.WriteString(v)
109}
110
111// separate writes the comma before an item, except right after a key.
112func (w *JSONWriter) separate() {
113	if w.afterKey {
114		w.afterKey = false
115		return
116	}
117	if n := len(w.hasItems); n > 0 {
118		if w.hasItems[n-1] {
119			w.b.WriteByte(',')
120		}
121		w.hasItems[n-1] = true
122	}
123}