json

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 8, 2023 License: MIT Imports: 4 Imported by: 0

README

Documentation Go workflow CircleCI codecov Go Report Card GitHub tag (latest SemVer)

json

Yet another json library. It's created to process unstructured json in a convenient and efficient way.

There is also some set of jq filters implemented on top of json.Decoder.

json usage

Decoder is stateless. Most of the methods take source buffer and index where to start parsing and return a result and index where they stopped parsing.

None of methods make a copy or allocate except these which take destination buffer in arguments.

The code is from examples.

// Parsing single object.

var d json.Decoder
data := []byte(`{"key": "value", "another": 1234}`)

i := 0 // initial position
i, err := d.Enter(data, i, json.Object)
if err != nil {
	// not an object
}

var key []byte // to not to shadow i and err in a loop

// extracted values
var value, another []byte

for d.ForMore(data, &i, json.Object, &err) {
	key, i, err = d.Key(data, i) // key decodes a string but don't decode '\n', '\"', '\xXX' and others
	if err != nil {
		// ...
	}

	switch string(key) {
	case "key":
		value, i, err = d.DecodeString(data, i, value[:0]) // reuse value buffer if we are in a loop or something
	case "another":
		another, i, err = d.Raw(data, i)
	default: // skip additional keys
		i, err = d.Skip(data, i)
	}

	// check error for all switch cases
	if err != nil {
		// ...
	}
}
if err != nil {
	// ForMore error
}
// Parsing jsonl: newline (or space, or comma) delimited values.

var err error // to not to shadow i in a loop
var d json.Decoder
data := []byte(`"a", 2 3
["array"]
`)

for i := d.SkipSpaces(data, 0); i < len(data); i = d.SkipSpaces(data, i) { // eat trailing spaces and not try to read the value from string "\n"
	i, err = processOneObject(data, i) // do not use := here as it shadow i and loop will restart from the same index
	if err != nil {
		// ...
	}
}

jq usage

jq package is a set of Filters that take data from one buffer, process it, and add result to another buffer. Aside from buffers filters take src buffer start position and return where reader stopped.

Also there is a state taken and returned. It's used by filters to return multiple values one by one. The caller must provide nil on the first iteration and returned state on the rest of iterations. Iteration must stop when returned state is nil. Filter may or may not add a value to dst buffer. Empty filter for example adds no value and returns nil state.

Destination buffer is returned even in case of error. This is mostly done for avoiding allocs in case the buffer was grown but error happened.

The code is from examples.

// Extract some inside value.

data := []byte(`{"key0":"skip it", "key1": {"next_key": ["array", null, {"obj":"val"}, "trailing element"]}}  "next"`)

f := jq.Index{"key1", "next_key", 2} // string keys and int array indexes are supported

var res []byte // reusable buffer
var i int      // start index

// Most filters only parse single value and return index where the value ended.
// Use jq.ApplyToAll(f, res[:0], data, 0, []byte("\n")) to process all values in a buffer.
res, i, _, err := f.Next(res[:0], data, i, nil)
if err != nil {
	// i is an index in a source buffer where the error occurred.
}

fmt.Printf("value: %s\n", res)
fmt.Printf("final position: %d of %d\n", i, len(data)) // object was parsed to the end of the first value to be able to read next one
_ = i < len(data)                                      // but not the next value

// Output:
// value: {"obj":"val"}
// final position: 92 of 100

This is especially convenient if you need to extract a value from json inside base64 inside json. Yes, I've seen such cases and they motivated me to create this package.

// generated by command
// jq -nc '{key3: "value"} | {key2: (. | tojson)} | @base64 | {key1: .}'
data := []byte(`{"key1":"eyJrZXkyIjoie1wia2V5M1wiOlwidmFsdWVcIn0ifQ=="}`)

f := jq.NewPipe(
	jq.Index{"key1"},
	&jq.Base64d{
		Encoding: base64.StdEncoding,
	},
	&jq.JSONDecoder{},
	jq.Index{"key2"},
	&jq.JSONDecoder{},
	jq.Index{"key3"},
)

res, _, _, err := f.Next(nil, data, 0, nil)
if err != nil {
	panic(err)
}

// res is []byte(`"value"`)

Documentation

Index

Examples

Constants

View Source
const (
	None   = 0 // never returned in successful case
	Null   = 'n'
	Bool   = 'b'
	String = 's'
	Array  = '['
	Object = '{'
	Number = 'N'
)

Value types returned by Decoder.

Variables

View Source
var (
	ErrBadNumber   = errors.New("bad number")
	ErrBadRune     = errors.New("bad rune")
	ErrBadString   = errors.New("bad string")
	ErrEndOfBuffer = errors.New("unexpected end of buffer")
	ErrSyntax      = errors.New("syntax error")
	ErrType        = errors.New("incompatible type")
)

Errors returned by Decoder.

Functions

func SkipSpaces added in v0.6.0

func SkipSpaces(b []byte, i int) int

SkipSpaces skips whitespaces.

Types

type Decoder added in v0.7.0

type Decoder struct{}

Decoder is a group of methods to parse JSON buffers. Decoder is stateless. All the needed state is passed though arguments and return values.

Most of the methods take buffer with json and start position and return a value, end position and possible error.

Example
package main

import (
	"fmt"

	"nikand.dev/go/json"
)

func main() {
	var d json.Decoder
	data := []byte(`{"key": "value", "another": 1234}`)

	i := 0 // initial position
	i, err := d.Enter(data, i, json.Object)
	if err != nil {
		// not an object
	}

	var key []byte // to not to shadow i and err in a loop

	// extracted values
	var value, another []byte

	for d.ForMore(data, &i, json.Object, &err) {
		key, i, err = d.Key(data, i) // key decodes a string but don't decode '\n', '\"', '\xXX' and others
		if err != nil {
			// ...
		}

		switch string(key) {
		case "key":
			value, i, err = d.DecodeString(data, i, value[:0]) // reuse value buffer if we are in a loop or something
		case "another":
			another, i, err = d.Raw(data, i)
		default: // skip additional keys
			i, err = d.Skip(data, i)
		}

		// check error for all switch cases
		if err != nil {
			// ...
		}
	}
	if err != nil {
		// ForMore error
	}

	fmt.Printf("key: %s\nanother: %s\n", value, another)

}
Output:

key: value
another: 1234
Example (MultipleValues)
package main

import (
	"fmt"

	"nikand.dev/go/json"
)

func main() {
	var err error // to not to shadow i in a loop
	var d json.Decoder
	data := []byte(`"a", 2 3
["array"]
`)

	processOneObject := func(data []byte, st int) (int, error) {
		raw, i, err := d.Raw(data, st)

		fmt.Printf("value: %s\n", raw)

		return i, err
	}

	for i := d.SkipSpaces(data, 0); i < len(data); i = d.SkipSpaces(data, i) { // eat trailing spaces and not try to read the value from string "\n"
		i, err = processOneObject(data, i) // do not use := here as it shadow i and loop will restart from the same index
		if err != nil {
			// ...
		}
	}

}
Output:

value: "a"
value: 2
value: 3
value: ["array"]

func (*Decoder) Break added in v0.7.0

func (d *Decoder) Break(b []byte, st, depth int) (i int, err error)

Break breaks from inside the object to the end of it on depth levels. As a special case with depth=0 it skips the next value. Skip and Raw do exactly that.

It's intended for exiting out of arrays and objects when their content is not needed anymore (all the needed indexes or keys are already parsed) and we want to parse the next array or object.

func (*Decoder) DecodeString added in v0.7.0

func (d *Decoder) DecodeString(b []byte, st int, buf []byte) (s []byte, i int, err error)

DecodeString reads the next string, decodes escape sequences (\n, \uXXXX), and appends the result to the buf.

func (*Decoder) DecodedStringLength added in v0.7.0

func (d *Decoder) DecodedStringLength(b []byte, st int) (n, i int, err error)

DecodedStringLength reads and decodes the next string but only return the result length. It doesn't allocate while DecodeString does.

func (*Decoder) Enter added in v0.7.0

func (d *Decoder) Enter(b []byte, st int, typ byte) (i int, err error)

Enter enters an Array or an Object. typ is checked to match with the actual container type. Use More or, more convenient form, ForMore to iterate over container. See examples to understand the usage pattern more.

func (*Decoder) ForMore added in v0.7.0

func (d *Decoder) ForMore(b []byte, i *int, typ byte, errp *error) bool

ForMore is a convenient wrapper for More which makes iterating code shorter and simpler.

func (*Decoder) Key added in v0.7.0

func (d *Decoder) Key(b []byte, st int) (k []byte, i int, err error)

Key reads the next string removing quotes but not decoding the string value. So escape sequences (\n, \uXXXX) are not decoded. They are returned as is. This is intended for object keys as they usually contain alpha-numeric symbols only. This is faster and does not require additional buffer for decoding.

func (*Decoder) Length added in v0.7.0

func (d *Decoder) Length(b []byte, st int) (n, i int, err error)

Length calculates String length (runes in decoded form) or number of elements in Array or Object.

func (*Decoder) More added in v0.7.0

func (d *Decoder) More(b []byte, st int, typ byte) (more bool, i int, err error)

More iterates over an Array or an Object elements entered by the Enter method.

func (*Decoder) Raw added in v0.7.0

func (d *Decoder) Raw(b []byte, st int) (v []byte, i int, err error)

Raw skips the next value and returns subslice with the value trimming whitespaces.

func (*Decoder) Skip added in v0.7.0

func (d *Decoder) Skip(b []byte, st int) (i int, err error)

Skip skips the next value.

func (*Decoder) SkipSpaces added in v0.7.0

func (d *Decoder) SkipSpaces(b []byte, i int) int

SkipSpaces skips whitespaces.

func (*Decoder) Type added in v0.7.0

func (d *Decoder) Type(b []byte, st int) (tp byte, i int, err error)

Type finds the beginning of the next value and detects its type. It doesn't parse the value so it can't detect if it's incorrect.

type Encoder added in v0.7.0

type Encoder struct {
}

func (*Encoder) EncodeString added in v0.7.0

func (e *Encoder) EncodeString(w, s []byte) []byte

EncodeString encodes string in a JSON compatible way. It also adds quotes.

func (*Encoder) EncodeStringContent added in v0.7.0

func (e *Encoder) EncodeStringContent(w, s []byte) []byte

EncodeStringContent does the same as EncodeString but does not add quotes. It can be used to generate the string from multiple parts. Yet if a symbol designated to be escaped is split between parts it encodes each part of the symbol separately.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL