Documentation
¶
Overview ¶
Package jsonenc appends JSON to a byte slice: a string literal, an integer, and an object whose members are written in the order they are named. It is a kernel primitive — stdlib-only, and generic down to the signatures: a slice and a value, with no logger, no token and no codec in any of them. It registers no codec and reads no value by reflection; encoding/json stays the codec, and codec/json.Marshal does not change. This package is for the emitters that already write JSON by hand, so that the SDK has one string escaper and not four.
What a string becomes ¶
AppendString writes s between quotes the way encoding/json writes it, with one difference stated after the table:
" and \ a backslash before each
\b \f \n \r \t their short escapes
other < 0x20 \u00XX, lowercase hex
U+2028, U+2029 \u2028, \u2029, as encoding/json does, so that a
document pasted into a script is not cut at a line end
invalid UTF-8 each bad byte becomes U+FFFD, written as its three
bytes, so the output is always valid UTF-8
everything else as it is, including DEL (0x7f)
There is NO HTML escaping: '<', '>' and '&' are written as they are, as json.Encoder does after SetEscapeHTML(false) and as json.Marshal does not. A caller that embeds the result in HTML must escape it itself. That is the one difference from json.Marshal, which escapes them as \u003c, \u003e and \u0026, and the fuzz test holds the rest to it: every input decodes to the string json.Marshal's output decodes to, and valid input is byte for byte what a json.Encoder with HTML escaping off writes.
How it is fast ¶
The string is searched a word at a time for the next byte that needs attention — a quote, a backslash, a byte below 0x20 or a byte from 0x80 up — by the first-match search of internal/kernel/swar, and everything before it is copied in one append. A string of ASCII with nothing to escape, which is most of them, costs one search and one copy. A byte from 0x80 up is where the slow path starts: the run of non-ASCII is decoded rune by rune, validated, and copied whole unless it holds a bad byte or a line separator.
Objects ¶
BeginObject opens an object on a slice, and each member method returns the object again, so a call chain reads as the document and writes members in exactly the order it names them: there is no sorting, no map and no duplicate check. A key is escaped like any string. Object.End closes it and returns the slice. An object is a value of two words and allocates nothing; the slice it grows is the caller's.
Cost ¶
Nothing here allocates once the slice has the capacity, and BENCH.md has the figures against encoding/json and the byte loops this replaced.
Index ¶
- func AppendEscaped(dst []byte, s string) []byte
- func AppendInt(dst []byte, v int64) []byte
- func AppendString(dst []byte, s string) []byte
- func AppendUint(dst []byte, v uint64) []byte
- type Object
- func (o Object) Bool(key string, v bool) Object
- func (o Object) End() []byte
- func (o Object) Int(key string, v int64) Object
- func (o Object) Null(key string) Object
- func (o Object) Raw(key string, raw []byte) Object
- func (o Object) String(key, value string) Object
- func (o Object) Uint(key string, v uint64) Object
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AppendEscaped ¶
AppendEscaped appends the body of the JSON string literal of s — what AppendString writes between its quotes — so a caller can join several pieces into one literal, a group prefix and a key, before the closing quote.
func AppendInt ¶
AppendInt appends v to dst in decimal, as a JSON number, and returns the extended slice.
func AppendString ¶
AppendString appends s to dst as a JSON string literal, quotes included, and returns the extended slice. See the package comment for the exact escapes: invalid UTF-8 becomes U+FFFD, U+2028 and U+2029 are escaped, and '<', '>' and '&' are not.
Example ¶
ExampleAppendString quotes a string with a quote, a newline and a character JavaScript would end a line on.
package main
import (
"fmt"
"github.com/kitsunium/sdk/internal/kernel/jsonenc"
)
func main() {
fmt.Println(string(jsonenc.AppendString(nil, "say \"hi\"\n<b>\u2028")))
}
Output: "say \"hi\"\n<b>\u2028"
func AppendUint ¶
AppendUint appends v to dst in decimal, as a JSON number, and returns the extended slice. A value past 2^53 is exact here and is not in a JavaScript reader; write it as a string when the reader is one.
Types ¶
type Object ¶
type Object struct {
// contains filtered or unexported fields
}
Object is an object being written: the slice so far, and whether a member has been written yet, which decides the comma. It is a value — every method returns the object to carry on with, and the one the caller still holds is stale — and the zero Object is not usable: start with BeginObject.
func BeginObject ¶
BeginObject appends the opening brace of an object to dst and returns the object that writes its members after it.
Example ¶
ExampleBeginObject writes members in the order they are named.
package main
import (
"fmt"
"github.com/kitsunium/sdk/internal/kernel/jsonenc"
)
func main() {
out := jsonenc.BeginObject(nil).
String("level", "info").
Int("n", -7).
Bool("ok", true).
Null("err").
Raw("nested", jsonenc.BeginObject(nil).Uint("id", 9).End()).
End()
fmt.Println(string(out))
}
Output: {"level":"info","n":-7,"ok":true,"err":null,"nested":{"id":9}}
func (Object) Raw ¶
Raw writes the member key with raw, which the caller guarantees is one valid JSON value: it is copied as it is, so a nested object built by another Object, or a number the caller formatted, goes in without a second escape.
func (Object) String ¶
String writes the member key with the string value, both escaped as AppendString escapes.