jsonw.gno
2.87 Kb · 135 lines
1// Package jsonw builds JSON text for realm query endpoints.
2//
3// Values are plain strings holding encoded JSON, composed with Obj and Arr:
4//
5// jsonw.Obj(
6// "id", jsonw.Uint(b.ID),
7// "title", jsonw.String(b.Title),
8// "tags", jsonw.Strings(b.Tags),
9// )
10//
11// String escapes everything JSON requires and replaces invalid UTF-8, so
12// user-supplied text can never break the document.
13package jsonw
14
15import (
16 "strconv"
17 "strings"
18 "time"
19 "unicode/utf8"
20)
21
22const Null = "null"
23
24// String encodes s as a JSON string.
25func String(s string) string {
26 var b strings.Builder
27 b.WriteByte('"')
28 for i := 0; i < len(s); {
29 r, size := utf8.DecodeRuneInString(s[i:])
30 if r == utf8.RuneError && size == 1 {
31 b.WriteString(`\ufffd`)
32 i++
33 continue
34 }
35 switch {
36 case r == '"':
37 b.WriteString(`\"`)
38 case r == '\\':
39 b.WriteString(`\\`)
40 case r == '\n':
41 b.WriteString(`\n`)
42 case r == '\r':
43 b.WriteString(`\r`)
44 case r == '\t':
45 b.WriteString(`\t`)
46 case r < 0x20 || r == 0x7f || r == 0x2028 || r == 0x2029:
47 b.WriteString(`\u`)
48 h := strconv.FormatInt(int64(r), 16)
49 for j := len(h); j < 4; j++ {
50 b.WriteByte('0')
51 }
52 b.WriteString(h)
53 default:
54 b.WriteString(s[i : i+size])
55 }
56 i += size
57 }
58 b.WriteByte('"')
59 return b.String()
60}
61
62// Int encodes n. Values beyond JavaScript's safe integer range are encoded
63// as strings so clients never lose precision.
64func Int(n int64) string {
65 if n > 9007199254740991 || n < -9007199254740991 {
66 return `"` + strconv.FormatInt(n, 10) + `"`
67 }
68 return strconv.FormatInt(n, 10)
69}
70
71// Uint encodes n, as a string beyond JavaScript's safe integer range.
72func Uint(n uint64) string {
73 if n > 9007199254740991 {
74 return `"` + strconv.FormatUint(n, 10) + `"`
75 }
76 return strconv.FormatUint(n, 10)
77}
78
79// Bool encodes v.
80func Bool(v bool) string {
81 if v {
82 return "true"
83 }
84 return "false"
85}
86
87// Time encodes t as RFC 3339 in UTC, or null when zero.
88func Time(t time.Time) string {
89 if t.IsZero() {
90 return Null
91 }
92 return String(t.UTC().Format(time.RFC3339))
93}
94
95// Obj builds an object from alternating keys and encoded values.
96func Obj(kv ...string) string {
97 if len(kv)%2 != 0 {
98 panic("jsonw: Obj needs key/value pairs")
99 }
100 var b strings.Builder
101 b.WriteByte('{')
102 for i := 0; i < len(kv); i += 2 {
103 if i > 0 {
104 b.WriteByte(',')
105 }
106 b.WriteString(String(kv[i]))
107 b.WriteByte(':')
108 b.WriteString(kv[i+1])
109 }
110 b.WriteByte('}')
111 return b.String()
112}
113
114// Arr builds an array from encoded values.
115func Arr(items []string) string {
116 return "[" + strings.Join(items, ",") + "]"
117}
118
119// Strings encodes a list of strings.
120func Strings(ss []string) string {
121 out := make([]string, len(ss))
122 for i, s := range ss {
123 out[i] = String(s)
124 }
125 return Arr(out)
126}
127
128// Addresses encodes a list of addresses as strings.
129func Addresses(as []address) string {
130 out := make([]string, len(as))
131 for i, a := range as {
132 out[i] = String(a.String())
133 }
134 return Arr(out)
135}