jp

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Apr 8, 2026 License: MIT Imports: 16 Imported by: 5

README

jp is a byte buffer oriented library for parsing and manipulating JSON.

jp is a fork of jsonparser. It keeps the fast zero-allocation method for accessing individual values in a JSON document, and adds a few new features:

  • Index allows you to parse a buffer full of JSON once and then read and manipulate it in various ways with minimal additional CPU and memory overhead.

  • Patch allows you to create, manipulate, and apply JSON patches without needing excess remarshalling or memory allocation.

Rationale

The original jsonparser library is great for retrieving arbitrary values from JSON documents, but when you have to do a lot of it to the same (potentially large) document or make a lot of modifications to the document (which may be interspersed with additional reads), the overhead of reparsing the JSON can start to outweigh the benefits of having zero allocations.

Otherwise, it maintains the original jsonparser virtues of being easy to use, low overhead, and high performance when you don't want to marshal to and from a structure or a map.

Example

For the given JSON our goal is to extract the user's full name, number of github followers and avatar.

import "gitlab.com/rackn/jp"

...

data := []byte(`{
  "person": {
    "name": {
      "first": "Leonid",
      "last": "Bugaev",
      "fullName": "Leonid Bugaev"
    },
    "github": {
      "handle": "buger",
      "followers": 109
    },
    "avatars": [
      { "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", "type": "thumbnail" }
    ]
  },
  "company": {
    "name": "Acme"
  }
}`)

// You can specify key path by providing arguments to Get function
jp.Get(data, "person", "name", "fullName")

// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type
jp.GetInt(data, "person", "github", "followers")

// When you try to get object, it will return you []byte slice pointer to data containing it
// In `company` it will be `{"name": "Acme"}`
jp.Get(data, "company")

// If the key doesn't exist it will throw an error
var size int64
if value, err := jp.GetInt(data, "company", "size"); err == nil {
  size = value
}

// You can use `ArrayEach` helper to iterate items [item1, item2 .... itemN]
jp.ArrayEach(data, func(value []byte, dataType jp.ValueType, offset int, err error) {
	fmt.Println(jp.Get(value, "url"))
}, "person", "avatars")

// Or use can access fields by index!
jp.GetString(data, "person", "avatars", "[0]", "url")

// Non-braced numeric indexes work too
jp.GetString(data, "person", "avatars", "0", "url")

// So do negative indexes -- they count from the end of the array, but incur additional overhead.
jp.GetString(data, "person", "avatars", "-1", "url")

// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN }
jp.ObjectEach(data, func(key []byte, value []byte, dataType jp.ValueType, offset int) error {
        fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType)
	return nil
}, "person", "name")

// If you want to fold, spindle, or mutilate your JSON, make an Index out of it:
idx, err := jp.MakeIndex(data)

// Now, you can make whatever changes you want in a lazy fashion:
idx.Delete("person","github")
idx.Add(`{"handle":"rackn","followers":0}`,"person","gitlab")

// And then create a new document with the changes:
data,_ = index.MarshalJSON()
// For more information see docs below

Reference

You can view the API on pkg.go.dev.

On keys ...string parameters

You will see that most of the functions take a parameter named keys as their final variable, with a type signature of ...string. All these functions can descend to arbitrary locations in a set of nested JSON objects and arrays in any combination.

When one of the functions is descending into a JSON object, it interprets whatever member of keys (the key) it is considering as a string that should match an object key.

When descending into an Array, the key must be in one of the following forms:

  • A positive or negative integer. Values from 0 up refer to array indexes from the beginning of an array, and values from -1 down refer to indexes from the end of the array.
  • As above but surrounded by []. This is the format that jsonparser supported, presumably for disambiguation.
  • [-] or - when calling index.Add. This allows callers to append a value at the end of an array without knowing its length beforehand.

What makes it so fast?

  • All of the core functions do not allocate and only parse through byte buffers. The values they return are slices of the byte buffer they were passed
  • Creating Index values using IndexValue is single-pass and only parses enough to create a full Index of a single JSON object. It allocates the minimum amount of memory necessary to hold the key indexes, and initially maps all values directly to the buffer it was originally created from.
  • Modifying values using Index methods do not modify the backing byte array.
  • No automatic type conversions, by default everything is a []byte, but it provides you value type, so you can convert by yourself using provided helpers.
  • Does not parse full record, only keys you specified when using the core functions.

Documentation

Overview

Package jp provides fast JSON parsing, querying, patching, and structural comparison. It offers two complementary APIs:

  1. Streaming parser: Fast, zero-allocation operations on raw []byte JSON without unmarshaling. Core functions: Get, GetString, GetInt, GetBoolean, ArrayEach, ObjectEach. Returns slices of the input buffer - no allocation when reading.

  2. Index: A flat-buffer structure that encodes the full parse tree as integer-offset nodes. Supports O(log n) key lookups via binary search, mutation via copy-on-write, and zero-copy transport via IndexBytes/OpenIndex.

Both APIs avoid type conversions by default (everything is []byte with a ValueType marker), making them ideal for high-performance JSON manipulation without marshaling overhead.

Example: Streaming Parser

jp.Get(data, "person", "name", "fullName")
jp.GetInt(data, "person", "github", "followers")
jp.ArrayEach(data, callback, "person", "avatars")

Example: Index with Mutation

idx, _ := jp.MakeIndex(data)
idx.Delete("person", "github")
idx.Add(`{"handle":"rackn"}`, "person", "gitlab")
data, _ := idx.MarshalJSON()

Index Buffer Format

The Index type supports serialization via IndexBytes and reconstruction via OpenIndex. The on-disk format (v5/v6) uses variable-width encoding to minimize memory footprint: uint8, uint16, or uint32 offsets depending on container size. Compact() rebuilds a fresh index with optimal encoding and only live nodes.

Index

Constants

View Source
const (
	// NotExist indicates that the requested key path was not found.
	NotExist = ValueType(iota)
	// String is a JSON string value.
	String
	// Number is a JSON number value.
	Number
	// Object is a JSON object value.
	Object
	// Array is a JSON array value.
	Array
	// Boolean is a JSON boolean value (true or false).
	Boolean
	// Null is a JSON null literal.
	Null
	// Unknown is returned when the value type cannot be determined.
	Unknown = ValueType(255)
)
View Source
const ContentType = "application/json-patch+json"

ContentType is the MIME type for JSON Patch documents (RFC 6902).

View Source
const MaxKeyLen = 16 * 1024

MaxKeyLen is the maximum byte length of an object key. Object key lengths are stored as uint16 in the index buffer, so keys longer than MaxKeyLen would be silently truncated; instead, the library rejects them with KeyTooLongError.

Variables

View Source
var (
	// StopIteration is returned from an [ObjectEach] callback to terminate iteration early.
	StopIteration = errors.New("stop iteration")
	// KeyPathNotFoundError is returned when a key path does not resolve to a value.
	KeyPathNotFoundError = errors.New("Key path not found")
	// KeyPathFoundError is returned by the "missing" patch op when the path unexpectedly exists.
	KeyPathFoundError = errors.New("Key path exists")
	// UnknownValueTypeError is returned when a JSON value cannot be classified.
	UnknownValueTypeError = errors.New("Unknown value type")
	// MalformedJsonError is returned when the input is not valid JSON.
	MalformedJsonError = errors.New("Malformed JSON error")
	// MalformedStringError is returned when a JSON string is missing its closing quote.
	MalformedStringError = errors.New("Value is string, but can't find closing '\"' symbol")
	// MalformedArrayError is returned when a JSON array is missing its closing bracket.
	MalformedArrayError = errors.New("Value is array, but can't find closing ']' symbol")
	// MalformedObjectError is returned when a JSON object is missing its closing brace.
	MalformedObjectError = errors.New("Value looks like object, but can't find closing '}' symbol")
	// MalformedValueError is returned when a JSON value has no recognizable terminator.
	MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol")
	// OverflowIntegerError is returned when an integer value exceeds int64 range.
	OverflowIntegerError = errors.New("Value is number, but overflowed while parsing")
	// MalformedNumberError is returned when a number has invalid syntax.
	MalformedNumberError = errors.New("Value is number, but starts '0' or '-0' or is an invalid float")
	// MalformedStringEscapeError is returned when a string contains an invalid escape sequence.
	MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string")
	// NotStringError is returned when a value was expected to be a string but is not.
	NotStringError = errors.New("Value is not a string")
	// NotFloatError is returned when a value was expected to be a float but is not.
	NotFloatError = errors.New("Value is not a float")
	// NotIntegerError is returned when a value was expected to be an integer but is not.
	NotIntegerError = errors.New("Value is not an integer")
	// NotBooleanError is returned when a value was expected to be a boolean but is not.
	NotBooleanError = errors.New("Value is not a boolean")
	// NotNullError is returned when a value was expected to be null but is not.
	NotNullError = errors.New("Value is not null")
	// NotEqualError is returned when a JSON Patch "test" op finds unequal values.
	NotEqualError = errors.New("Values are not equal")
	// BadMoveError is returned when a JSON Patch "move" op would move a value into itself.
	BadMoveError = errors.New("Cannot move a value into itself")
	// BadPatchOpError is returned when a patch operation is invalid or unrecognized.
	BadPatchOpError = errors.New("Invalid patch Op")
	// NotArrayError is returned when a value was expected to be an array but is not.
	NotArrayError = errors.New("Value is not an array")
	// NotObjectError is returned when a value was expected to be an object but is not.
	NotObjectError = errors.New("Value is not an object")
	// IndexVersionError is returned by [OpenIndex] when the buffer format version is unrecognized.
	IndexVersionError = errors.New("Index buffer format version not recognized")
	// KeyTooLongError is returned when an object key exceeds [MaxKeyLen] bytes.
	KeyTooLongError = errors.New("object key exceeds maximum length")
)

Sentinel errors returned by parsing, querying, and patching operations.

View Source
var (
	// ErrMissingOp indicates that a patch operation is missing its "op" field.
	ErrMissingOp = errors.New("missing op")
	// ErrMissingPath indicates that a patch operation is missing its "path" field.
	ErrMissingPath = errors.New("missing path")
)
View Source
var IllegalPointerError = fmt.Errorf("illegal pointer")

IllegalPointerError is returned when a string is not a valid JSON Pointer.

Functions

func ArrayEach

func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error) error, keys ...string) error

ArrayEach iterates over the elements of a JSON array, invoking cb for each element. The optional keys traverse to a nested array before iterating. Return StopIteration from the callback to stop iteration early.

Callback signature: func(value []byte, dataType ValueType, offset int, err error) error

func Equal

func Equal(a, b []byte) bool

Equal reports whether the JSON documents a and b are structurally equal. It first compares the raw bytes directly. If they differ, it parses both into an Index and performs a deep structural comparison.

func GetBigInt added in v0.9.5

func GetBigInt(data []byte, keys ...string) (*big.Int, error)

GetBigInt returns the big.Int value at the given key path. It returns an error if the key path is not found or the value is not a number. Use this when GetInt returns OverflowIntegerError.

func GetBoolean

func GetBoolean(data []byte, keys ...string) (val bool, err error)

GetBoolean returns the bool value at the given key path. It returns an error if the key path is not found or the value is not a boolean.

func GetFloat

func GetFloat(data []byte, keys ...string) (val float64, err error)

GetFloat returns the float64 value at the given key path. It returns an error if the key path is not found or the value is not a number.

func GetInt

func GetInt(data []byte, keys ...string) (val int64, err error)

GetInt returns the int64 value at the given key path. It returns an error if the key path is not found or the value is not a number.

func GetNull

func GetNull(data []byte, keys ...string) error

GetNull returns an error if the value at keys is not a literal null.

func GetString

func GetString(data []byte, keys ...string) (val string, err error)

GetString returns the string value at the given key path, handling JSON unescaping and UTF-8 decoding. It returns an error if the key path is not found or the value is not a string.

func IsNull

func IsNull(b []byte) bool

IsNull reports whether b is a JSON null literal.

func ObjectEach

func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType) error, keys ...string) (err error)

ObjectEach iterates over the key-value pairs of a JSON object, invoking callback for each entry. The optional keys traverse to a nested object before iterating. Return StopIteration from the callback to stop early.

Callback signature: func(key []byte, value []byte, dataType ValueType) error

func ParseBigInt added in v0.9.5

func ParseBigInt(b []byte) (res *big.Int, err error)

ParseBigInt parses a JSON integer of arbitrary size into a big.Int. Use this when ParseInt returns OverflowIntegerError.

func ParseBoolean

func ParseBoolean(b []byte) (bool, error)

ParseBoolean parses a JSON boolean literal into a Go bool.

func ParseFloat

func ParseFloat(b []byte) (float64, error)

ParseFloat parses a JSON number value into a Go float64.

func ParseInt

func ParseInt(b []byte) (int64, error)

ParseInt parses a JSON number value into a Go int64.

func ParseString

func ParseString(b []byte) (string, error)

ParseString parses a JSON string value (including surrounding quotes) into a Go string, unescaping any JSON escape sequences.

func Unescape

func Unescape(in, out []byte) ([]byte, error)

Unescape unescapes the string contained in 'in' and returns it as a slice. If 'in' contains no escaped characters:

Returns 'in'.

Else, if 'out' is of sufficient capacity (guaranteed if cap(out) >= len(in)):

'out' is used to build the unescaped string and is returned with no extra allocation

Else:

A new slice is allocated and returned.

Types

type Cursor added in v0.10.0

type Cursor struct {
	// contains filtered or unexported fields
}

Cursor provides stack-based, allocation-free navigation through a JSON tree. It is created via (*Index).Cursor and shares the underlying json/idx buffers.

Cursor uses stack-allocated memory for depths ≤ 8, avoiding heap allocations. The returned *Index from Item() and Self() is valid only until the next Cursor method call.

Use an iterator to traverse all children:

for key, child := range c.All() {
    fmt.Printf("%s: %s\n", key, string(child.MarshalJSON()))
}

func (*Cursor) All added in v0.10.0

func (c *Cursor) All() iter.Seq2[string, *Index]

All returns an iterator over the direct children of this Cursor starting at the current cursor position.

For objects, keys are field names in sorted order For arrays, keys are decimal indices. For anything else, key will be an empty string.

IMPORTANT: The iterator owns the *Index that is returned. If you need to save it to do eomthing with it, make a deep copy of it with: myCopy := *value

func (*Cursor) Ascend added in v0.10.0

func (c *Cursor) Ascend(n int) (int, bool)

Ascend pops n levels up the cursor stack by calling Up repeatedly. It returns the number of levels actually ascended and true if all n Up calls succeeded. If the cursor reaches the root before n levels have been popped, it returns the count reached so far and false.

func (*Cursor) Descend added in v0.10.0

func (c *Cursor) Descend(keys ...string) (int, bool)

Descend navigates from the current position to the nested value identified by the key path keys. For each key except the last it calls Seek then Down to step inside that container. For the final key it calls Seek only, leaving the cursor positioned at that key within its parent container.

After a successful Descend, call Item to read the value, Down to enter it (if it is a container), or All to iterate its children.

Descend returns the index of the last key it attempted and true if every key was found. On failure the index identifies the missing key.

func (*Cursor) Down added in v0.10.0

func (c *Cursor) Down() bool

Down descends into the container child at the current position. The cursor is positioned at the first item of that container. Returns false if the child is a simple value (not a container).

func (*Cursor) First added in v0.10.0

func (c *Cursor) First() bool

First moves to the first position. Returns false if the container is empty.

func (*Cursor) Item added in v0.10.0

func (c *Cursor) Item() (string, *Index)

Item returns the key and value at the current position.

For objects the key is the field name; for arrays it is the decimal index. For a simple-value cursor (no container) the key is "".

The returned *Index is reused across calls — it is only valid until the next Cursor method call.

func (*Cursor) KeyBytes added in v0.10.0

func (c *Cursor) KeyBytes() []byte

KeyBytes returns the raw (unescaped) key bytes for the current object entry. Returns nil if the current container is an array. The returned slice is valid until the next Cursor method call.

func (*Cursor) Last added in v0.10.0

func (c *Cursor) Last() bool

Last moves to the last position. Returns false if the container is empty.

func (*Cursor) Len added in v0.10.0

func (c *Cursor) Len() int

Len returns the number of children in the current container, or 0 for a simple-value cursor or an empty container.

func (*Cursor) Next added in v0.10.0

func (c *Cursor) Next() bool

Next moves to the next sibling. Returns false if already at the last position.

func (*Cursor) Pos added in v0.10.0

func (c *Cursor) Pos() int

Pos returns the current zero-based position within the container.

func (*Cursor) Prev added in v0.10.0

func (c *Cursor) Prev() bool

Prev moves to the previous sibling. Returns false if already at the first position.

func (*Cursor) Seek added in v0.10.0

func (c *Cursor) Seek(k string) bool

Seek moves to the position of key k in the current container.

For objects, it binary-searches the sorted entries and positions the cursor at k (returning true) or at the insertion point (returning false).

For arrays, k is parsed as an integer index in either bare ("3") or bracketed ("[3]") form. Negative indices count from the end (-1 or [-1] is the last element). Returns true and positions the cursor if the index is valid; returns false without moving if k is not a valid index or is out of bounds.

func (*Cursor) Self added in v0.10.0

func (c *Cursor) Self() *Index

Self returns an Index for the container the cursor is currently working in. It is the direct equivalent of building an Index from the cursor's json/idx buffers and the current container offset.

The returned *Index is reused across calls — it is only valid until the next Cursor method call.

func (*Cursor) Up added in v0.10.0

func (c *Cursor) Up() bool

Up pops back to the parent container. The position is restored to where it was when Down was called. Returns false if already at the root.

type Index

type Index struct {
	// contains filtered or unexported fields
}

Index encodes a complete JSON parse tree as integer-offset nodes inside a flat byte slice (idx), paired with the original JSON buffer. An Index supports O(log n) key lookups, structural equality, mutation via copy-on-write, and zero-copy transport via [IndexBytes] / OpenIndex.

The Index type is safe for concurrent use by multiple goroutines when each goroutine operates on a distinct Index value. Methods that modify the receiver (Add, Replace, Delete, Compact) are not safe for concurrent use on the same value; these operations use copy-on-write internally but the receiver itself is mutated. For concurrent access to the same logical document, create distinct Index values via MarshalJSON/OpenIndex or use external synchronization.

func IndexValue

func IndexValue(payload []byte) (idx int, res *Index, err error)

IndexValue creates an Index for the first JSON value in buf. If IndexValue returns without an error, then idx points to the first position in buf after the JSON value that was parsed.

If IndexValue returns with an error, idx points to the start of the JSON value that caused the parse error.

func MakeIndex

func MakeIndex(json []byte, idx []byte) (*Index, error)

MakeIndex parses json into an Index, appending index nodes into idx. Pass nil (or an empty slice) to let MakeIndex allocate. Pass a pre-allocated slice to avoid a heap allocation — the caller retains ownership of the backing array. Unlike Index.Make, MakeIndex does not reset idx: it appends the new index after any existing content, preserving bytes already in the slice. This matters when the caller passes a non-empty buffer whose backing array must not be overwritten (e.g. Patch.Apply passing the previous result as scratch). The first 2 bytes of the appended index contain the format version.

func MustIndex

func MustIndex(buf []byte) *Index

MustIndex creates an Index from the first JSON value in buf, panicking on error.

func OpenIndex added in v0.10.0

func OpenIndex(json, idx []byte) (*Index, error)

OpenIndex reconstructs an Index from a JSON buffer and an index buffer previously produced by IndexBytes. The root node offset is read from the last 4 bytes of idx.

Versions 2, 3, 5, and 6 are loaded directly from the buffer. Version 4 stored narrow container offsets as absolute values, which is incompatible with the relative encoding used by v5+; any other unrecognized version may also be structurally incompatible. For these cases OpenIndex transparently rebuilds the index by re-parsing json — the idx buffer is reused as scratch space for the rebuild (equivalent to MakeIndex(json, idx[:0])), so callers can pass a pre-allocated buffer to avoid allocation on the rebuild path. The only errors returned are JSON parse errors from the rebuild path.

func (*Index) Add

func (ib *Index) Add(value []byte, keys ...string) error

Add inserts the JSON in value at the given key path. For arrays, the special key "-" appends to the end. For objects, it inserts at the sorted position (maintaining lexicographic key order). Returns KeyPathNotFoundError if the parent container does not exist, KeyTooLongError if the key exceeds MaxKeyLen.

func (*Index) AppendJSON added in v0.10.0

func (ib *Index) AppendJSON(buf []byte) ([]byte, error)

AppendJSON appends the serialized JSON for this node to buf and returns the extended buffer. Unlike MarshalJSON it lets the caller provide (and reuse) the output buffer, avoiding a bytes.Buffer heap allocation.

func (*Index) BigInt added in v0.10.0

func (ib *Index) BigInt() (*big.Int, error)

BigInt returns the *big.Int value of this Index. It returns an error if the current node is not a number. Use this when Int() returns OverflowIntegerError.

func (*Index) Bool added in v0.10.0

func (ib *Index) Bool() (bool, error)

Bool returns the bool value of this Index. It returns an error if the current node is not a boolean.

func (*Index) Compact added in v0.10.0

func (ib *Index) Compact() *Index

Compact returns a new Index with freshly serialized JSON and a freshly built index buffer derived from the current state of this Index. The result is fully self-contained with no dependency on the receiver's JSON buffer. The modified flag is cleared in the returned Index.

func (*Index) Cursor added in v0.10.0

func (ib *Index) Cursor() *Cursor

Cursor returns a Cursor for navigating the children of this Index node.

For container roots (array/object), the cursor starts at position 0. For simple roots (string/number/bool/null), only Item() is useful — it returns ("", value). All navigation methods return false.

func (*Index) Delete

func (ib *Index) Delete(keys ...string) error

Delete removes the value at the given key path from the Index.

func (*Index) Equal

func (ib *Index) Equal(other *Index) bool

Equal reports whether ib and other represent structurally equal JSON values.

func (*Index) Float added in v0.10.0

func (ib *Index) Float() (float64, error)

Float returns the float64 value of this Index. It returns an error if the current node is not a number.

func (*Index) Get

func (ib *Index) Get(keys ...string) (*Index, error)

Get returns a new Index for the value at the given key path. The returned Index shares the underlying json and idx buffers with the receiver; the receiver must remain valid for the lifetime of the returned Index.

For allocation-free lookups, see Index.GetInto.

func (*Index) GetBigInt added in v0.10.0

func (ib *Index) GetBigInt(keys ...string) (*big.Int, error)

GetBigInt is like Index.Get but returns the *big.Int value directly. Use this when GetInt returns OverflowIntegerError.

func (*Index) GetBoolean added in v0.10.0

func (ib *Index) GetBoolean(keys ...string) (val bool, err error)

GetBoolean is like Index.Get but returns the bool value directly. It returns an error if the key path is not found or the value is not a boolean.

func (*Index) GetFloat added in v0.10.0

func (ib *Index) GetFloat(keys ...string) (val float64, err error)

GetFloat is like Index.Get but returns the float64 value directly. It returns an error if the key path is not found or the value is not a number.

func (*Index) GetInt added in v0.10.0

func (ib *Index) GetInt(keys ...string) (val int64, err error)

GetInt is like Index.Get but returns the int64 value directly. It returns an error if the key path is not found or the value is not a number or if the value overflows int64.

func (*Index) GetInto added in v0.10.0

func (ib *Index) GetInto(dst *Index, keys ...string) error

GetInto is like Get but writes the result into dst instead of allocating a new Index on the heap. This avoids one allocation per call when the caller can supply a stack-local or reusable Index. The dst.Index shares the underlying buffers with the receiver.

func (*Index) GetNull added in v0.10.0

func (ib *Index) GetNull(keys ...string) error

GetNull is like Index.Get but returns an error if the value at the key path is not null.

func (*Index) GetString added in v0.10.0

func (ib *Index) GetString(keys ...string) (val string, err error)

GetString is like Index.Get but returns the string value directly, handling JSON unescaping. It returns an error if the key path is not found or the value is not a string.

func (*Index) IndexBytes added in v0.10.0

func (ib *Index) IndexBytes() []byte

IndexBytes returns the index buffer with the root node offset appended as the final 4 bytes (little-endian). Pass this, together with the original JSON, to OpenIndex to reconstruct the Index without re-parsing.

func (*Index) Int added in v0.10.0

func (ib *Index) Int() (int64, error)

Int returns the int64 value of this Index. It returns an error if the current node is not a number or if the value overflows int64.

func (*Index) Len added in v0.10.0

func (ib *Index) Len() int

Len returns the serialized JSON byte length of this node in O(1). If the Index is empty, returns 0.

func (*Index) Make added in v0.10.0

func (i *Index) Make(j []byte) error

Make parses j and builds the index into the receiver. If i.idx is already allocated its length is reset to zero before building, so the index occupies the buffer from the start; otherwise a fresh buffer is allocated. Callers that already hold an *Index (e.g. a stack-local or pool-recycled value) can avoid a heap allocation by calling Make directly instead of MakeIndex. Unlike MakeIndex, Make always resets i.idx before writing — do not use Make when the backing array of i.idx must not be overwritten. On success, modified is set to false. On error the receiver is left in an indeterminate state and should not be used.

func (*Index) MarshalJSON

func (ib *Index) MarshalJSON() ([]byte, error)

MarshalJSON implements encoding/json.Marshaler. For allocation-free serialization, see Index.AppendJSON.

func (*Index) Null added in v0.10.0

func (ib *Index) Null() error

Null returns an error if the current node is not a null value.

func (*Index) Open added in v0.10.0

func (i *Index) Open(json, idxBuf []byte) error

Open loads an index from a previously serialized buffer into the receiver, or rebuilds it from json if the buffer version is unrecognized. It is the in-place equivalent of OpenIndex: callers that already hold an *Index can avoid a heap allocation by calling Open directly. Versions 2, 3, and 5 are accepted directly; any other version triggers a transparent rebuild via [Make]. On success, modified is set to false.

func (*Index) Replace

func (ib *Index) Replace(value []byte, keys ...string) error

Replace replaces the value at the given key path with the JSON in value. It returns KeyPathNotFoundError if the path does not exist. The operation uses copy-on-write semantics: the receiver is mutated, but the original JSON buffer is not modified.

func (*Index) Rewrite added in v0.10.0

func (ib *Index) Rewrite() (*Index, error)

Rewrite returns a new Index with freshly serialized JSON and a freshly built index buffer derived from the current state of this Index. Unlike [Compact] which allocates with make([]byte, 0, ib.Len()), Rewrite accepts an optional pre-allocated output buffer. Returns the new Index or an error if JSON serialization fails.

func (*Index) String added in v0.10.0

func (ib *Index) String() (string, error)

String returns the string value of this Index. It returns an error if the current node is not a string.

func (*Index) Type

func (ib *Index) Type() ValueType

Type returns the ValueType of the root node of this Index.

func (*Index) WriteTo

func (ib *Index) WriteTo(w io.Writer) (int64, error)

WriteTo serializes the JSON represented by this Index into w.

type Op added in v0.9.3

type Op []byte

Op represents a single JSON Patch operation as defined by RFC 6902. It is the raw JSON bytes of one operation object. Supported operations:

"add"    - Add a value at the given path
"remove" - Remove the value at the given path
"replace"- Replace the value at the given path
"move"   - Move a value from one path to another
"copy"   - Copy a value from one path to another
"test"   - Test that the value at a path matches a given value
"missing"- Non-standard: fail if the path exists (for idempotent ops)

The "missing" op is a non-standard extension that returns KeyPathFoundError if the path exists, and succeeds if the path is absent.

func (Op) From added in v0.9.3

func (o Op) From() Pointer

From returns the "from" field as a Pointer, or nil if the field is absent. Only "move" and "copy" operations use this field.

func (Op) Op added in v0.9.3

func (o Op) Op() string

Op returns the "op" field of this operation. It panics if the field is missing.

func (Op) Path added in v0.9.3

func (o Op) Path() Pointer

Path returns the "path" field as a Pointer. It panics if the field is missing or is not a well-formed JSON Pointer.

func (Op) Valid added in v0.9.3

func (o Op) Valid() bool

Valid reports whether the operation has all required fields and valid values. It checks that the "op" field is present and recognized, the "path" field is a valid JSON Pointer, and any required fields (value for "add"/"replace"/"test", from for "move"/"copy") are present and well-formed.

func (Op) Value added in v0.9.3

func (o Op) Value() json.RawMessage

Value returns the raw JSON "value" field of this operation, or nil if absent. The returned value is of type json.RawMessage (a []byte alias) and must not be modified.

type Patch

type Patch []byte

Patch is a JSON Patch document (RFC 6902) stored as raw JSON bytes. In addition to the standard operations, it supports a non-standard "missing" operation that fails if the specified path already exists, enabling truly atomic guard semantics for additions.

func Generate

func Generate(base, target []byte, paranoid bool) (Patch, error)

Generate generates a JSON Patch that transforms base into target. If paranoid is true, each mutation op is preceded by a "test" op that verifies the old value before applying the change. Both base and target must be valid JSON. Returns a complete RFC 6902 JSON Patch document.

func GenerateFull

func GenerateFull(base, target []byte, paranoid, pretest bool) (Patch, error)

GenerateFull generates a JSON Patch that transforms base into target. If paranoid is true, each mutation op is preceded by a per-field "test" op. If pretest is true, a single "test" op for the entire base document is prepended instead (overriding paranoid). Both base and target must be valid JSON. Returns a complete RFC 6902 JSON Patch document.

func MakePatch added in v0.9.2

func MakePatch(opts PatchOpts, base, target []byte) (Patch, error)

MakePatch generates a JSON Patch that transforms base into target, controlled by opts. Both base and target must be valid JSON.

func NewPatch

func NewPatch(buf []byte) (res Patch, err error)

NewPatch parses buf as a JSON Patch document, validating each operation.

func (Patch) Apply

func (p Patch) Apply(base []byte, buf []byte) (result []byte, err error, loc int)

Apply applies the patch to base, which must be valid JSON. It returns the resulting JSON, any error, and the zero-based index of the failing Op (or 0 on success). The buf parameter is used as scratch space for index construction; callers can pass the previous result back as buf to amortize allocation costs across calls. The returned result never aliases buf, so it is safe to retain across multiple calls.

func (Patch) MarshalJSON added in v0.9.3

func (p Patch) MarshalJSON() ([]byte, error)

MarshalJSON implements encoding/json.Marshaler.

func (Patch) Ops added in v0.9.3

func (p Patch) Ops() []Op

Ops returns the individual patch operations, primarily for debugging.

func (*Patch) UnmarshalJSON added in v0.9.3

func (p *Patch) UnmarshalJSON(buf []byte) error

UnmarshalJSON implements encoding/json.Unmarshaler.

type PatchOpts added in v0.9.2

type PatchOpts struct {
	// TestIndividualChanges adds a "test" op before every "remove" or
	// "replace" op, verifying the old value before mutating it. "add"
	// ops for new object fields do not receive a "test" because the
	// field does not yet exist; use SupportMissingOp for that case.
	TestIndividualChanges bool
	// SupportMissingOp emits a non-standard "missing" op before "add"
	// ops on new fields. The "missing" op passes only if the path does
	// not exist, providing truly atomic guard semantics for additions.
	SupportMissingOp bool
	// TestFullBase prepends a single "test" op that asserts the entire
	// base document, disabling all per-field tests. Use this only when
	// you want to guard the full object rather than individual changes,
	// as the resulting patch may reject any concurrent modification.
	TestFullBase bool
}

PatchOpts controls how MakePatch generates a JSON Patch.

type Patcher added in v0.9.3

type Patcher struct {
	// contains filtered or unexported fields
}

Patcher builds a JSON Patch programmatically by accumulating operations. A zero-value Patcher is ready for use.

func (*Patcher) Add added in v0.9.3

func (p *Patcher) Add(path Pointer, value any)

Add appends an "add" operation that inserts value at path.

func (*Patcher) Append added in v0.9.3

func (p *Patcher) Append(other Patch)

Append copies all operations from other into the patch being built.

func (*Patcher) Copy added in v0.9.3

func (p *Patcher) Copy(path, from Pointer)

Copy appends a "copy" operation that duplicates the value from from to path.

func (*Patcher) Missing added in v0.9.3

func (p *Patcher) Missing(path Pointer)

Missing appends a non-standard "missing" operation that passes only if the given path does not exist.

func (*Patcher) Move added in v0.9.3

func (p *Patcher) Move(path, from Pointer)

Move appends a "move" operation that relocates the value from from to path.

func (*Patcher) Op added in v0.9.3

func (p *Patcher) Op(op string, path, from Pointer, value any)

Op appends a single operation to the patch being built. The value parameter may be an *Index, encoding/json.RawMessage, encoding/json.Marshaler, or any value accepted by encoding/json.Marshal.

func (*Patcher) Patch added in v0.9.3

func (p *Patcher) Patch() (Patch, error)

Patch finalizes the accumulated operations and returns the resulting Patch. If any operation recorded an error, or the resulting JSON is invalid, an error is returned along with the raw bytes for diagnostics. After Patch returns the Patcher is reset and ready for reuse.

func (*Patcher) Remove added in v0.9.3

func (p *Patcher) Remove(path Pointer)

Remove appends a "remove" operation that deletes the value at path.

func (*Patcher) Replace added in v0.9.3

func (p *Patcher) Replace(path Pointer, value any)

Replace appends a "replace" operation that overwrites the value at path.

func (*Patcher) Test added in v0.9.3

func (p *Patcher) Test(path Pointer, val any)

Test appends a "test" operation that asserts val exists at path.

type Pointer

type Pointer []byte

Pointer is a JSON Pointer (RFC 6901) stored as a byte slice. It extends the standard with negative array indices that count from the end.

Example usage:

Ptr("/users/-1/name")          // Last user's name
Ptr("/users/0")                // First user
PtrTo("users", "-1", "name")   // Same as Ptr("/users/-1/name")

Pointer values are safe for concurrent use by multiple goroutines when each goroutine operates on a distinct Pointer value.

func NewPointer

func NewPointer(s string) (Pointer, error)

NewPointer parses s as an RFC 6901 JSON Pointer. It returns an error if s is not a valid pointer.

func Ptr added in v0.9.3

func Ptr(s string) Pointer

Ptr is like NewPointer but panics on an invalid pointer string.

func PtrTo added in v0.9.3

func PtrTo(s ...string) Pointer

PtrTo constructs a Pointer from the given path fragments. It is the inverse of Pointer.String. Each fragment is JSON-Pointer-escaped internally. For negative array indices, use the string "-1", "-2", etc.

func (Pointer) Append

func (p Pointer) Append(frag string) Pointer

Append returns a new Pointer with frag appended as an additional path segment.

func (Pointer) Chop added in v0.9.1

func (p Pointer) Chop() (string, Pointer)

Chop removes and returns the last path segment and the remaining pointer.

func (Pointer) Contains

func (p Pointer) Contains(q Pointer) bool

Contains reports whether q refers to a value contained within p. For example, ["/users/0"] contains ["/users/0/name"].

func (Pointer) Equal added in v0.9.3

func (p Pointer) Equal(other Pointer) bool

Equal reports whether p and other refer to the same path.

func (Pointer) MarshalJSON

func (p Pointer) MarshalJSON() ([]byte, error)

MarshalJSON implements encoding/json.Marshaler.

func (Pointer) Path

func (p Pointer) Path() []string

Path splits the pointer into decoded path segments suitable for use as the keys arguments accepted by Get, Index.Get, and similar functions.

func (Pointer) Shift added in v0.9.1

func (p Pointer) Shift() (string, Pointer)

Shift removes and returns the first path segment and the remaining pointer.

func (Pointer) String

func (p Pointer) String() string

String returns the pointer as a string.

func (*Pointer) UnmarshalJSON

func (p *Pointer) UnmarshalJSON(buf []byte) error

UnmarshalJSON implements encoding/json.Unmarshaler.

func (Pointer) Valid added in v0.9.3

func (p Pointer) Valid() bool

Valid reports whether p has no improper ~ escapes and is otherwise well-formed.

type ValueType

type ValueType byte

ValueType identifies the JSON type of a parsed value.

func Get

func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error)

Get extracts a value from the raw JSON in data at the given key path.

Multiple keys may be provided to traverse nested structures (e.g. Get(data, "user", "name")). If no keys are given, Get returns the first complete JSON value in data.

It returns the raw bytes of the value, its ValueType, the byte offset past the value in data, and any error. If the key path is not found, dataType is NotExist and err is KeyPathNotFoundError.

The returned []byte slices point into the input data buffer and must not be modified. They are valid only as long as the data buffer remains unchanged.

func (ValueType) String

func (vt ValueType) String() string

String returns the human-readable name of the value type.

Jump to

Keyboard shortcuts

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