astjson

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 20, 2026 License: MIT Imports: 10 Imported by: 14

README

Build Status Go Reference

astjson - fast JSON parser and validator for Go

Features

  • Fast. Up to 15x faster than the standard encoding/json. See benchmarks.
  • Parses arbitrary JSON without schema, reflection, struct magic and code generation.
  • Optional arena allocation for zero-GC-overhead parsing in high-throughput paths.
  • Outperforms jsonparser and gjson when accessing multiple unrelated fields, since astjson parses the input JSON only once.
  • Validates the parsed JSON unlike jsonparser and gjson.
  • Extract, modify, and re-serialize parts of JSON with Value.Get(...).MarshalTo, Del, and Set.
  • Parses arrays containing values with distinct types (non-homogenous).
  • Preserves the original order of object keys when calling Object.Visit.

Known limitations

  • Not safe for concurrent use. Use per-goroutine Parser, Value, and Scanner instances.
  • Cannot parse JSON from io.Reader. Use Scanner for parsing a stream of JSON values from a string.

Usage

One-liner field access

For quick, single-field extraction from []byte input:

s := []byte(`{"foo": [123, "bar"]}`)
fmt.Printf("foo.0=%d\n", astjson.GetInt(s, "foo", "0"))
// Output: foo.0=123

Other one-liners: GetString, GetBytes, GetFloat64, GetBool, Exists.

Parsing with a Parser

When you need multiple fields from the same JSON, parse once and query:

var p astjson.Parser
v, err := p.Parse(`{
    "str": "bar",
    "int": 123,
    "float": 1.23,
    "bool": true,
    "arr": [1, "foo", {}]
}`)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("str=%s\n", v.GetStringBytes("str"))
fmt.Printf("int=%d\n", v.GetInt("int"))
fmt.Printf("float=%f\n", v.GetFloat64("float"))
fmt.Printf("bool=%v\n", v.GetBool("bool"))
fmt.Printf("arr.1=%s\n", v.GetStringBytes("arr", "1"))

Use Get for deep path access. Array elements are accessed by index as a string key:

v.Get("users", "0", "name")          // first user's name
v.GetInt("matrix", "1", "2")         // matrix[1][2] as int
Arena-mode parsing

Arena mode allocates all parsed values on a monotonic arena instead of the heap, eliminating GC pressure. See GC & Arena Safety for the rules.

a := arena.NewMonotonicArena()
var p astjson.Parser
v, err := p.ParseWithArena(a, `{"name": "alice", "age": 30}`)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("name=%s\n", v.GetStringBytes("name"))
// v is valid for the lifetime of a

The input string is copied onto the arena automatically — the caller can drop the reference to it immediately after parsing.

Creating values

Value constructors accept an arena.Arena — pass nil for heap allocation:

a := arena.NewMonotonicArena()

s := astjson.StringValue(a, "hello")      // arena-allocated string
n := astjson.IntValue(a, 42)              // arena-allocated number
f := astjson.FloatValue(a, 3.14)          // arena-allocated float
b := astjson.TrueValue(a)                 // arena-allocated true
obj := astjson.ObjectValue(a)             // empty object
arr := astjson.ArrayValue(a)              // empty array

// Heap-allocated (pass nil):
heapStr := astjson.StringValue(nil, "heap")

Also available: FalseValue, NumberValue (raw numeric string), StringValueBytes.

Modifying JSON

Set object fields:

a := arena.NewMonotonicArena()
v, _ := p.ParseWithArena(a, `{"foo": 1}`)
v.Set(a, "foo", astjson.StringValue(a, "updated"))
v.Set(a, "bar", astjson.IntValue(a, 2))
fmt.Println(v) // {"foo":"updated","bar":2}

Delete fields:

v.Del("foo")        // delete from object
v.Del("0")          // delete index 0 from array

Set array items:

arr, _ := p.ParseWithArena(a, `[1, 2, 3]`)
arr.SetArrayItem(a, 1, astjson.StringValue(a, "two"))
fmt.Println(arr) // [1,"two",3]

Append to array:

arr := astjson.ArrayValue(a)
astjson.AppendToArray(a, arr, astjson.IntValue(a, 1))
astjson.AppendToArray(a, arr, astjson.IntValue(a, 2))
fmt.Println(arr) // [1,2]

Set deeply nested paths (creates intermediate objects):

v := astjson.MustParse(`{}`)
astjson.SetValue(nil, v, astjson.IntValue(nil, 42), "a", "b", "c")
fmt.Println(v) // {"a":{"b":{"c":42}}}
Merging values

MergeValues recursively merges two values. For objects, keys from b are added to or replace keys in a. For arrays, elements are merged pairwise (arrays must have equal length). For scalars, b replaces a when they differ.

a := arena.NewMonotonicArena()
var p astjson.Parser
base, _ := p.ParseWithArena(a, `{"name": "alice", "age": 30}`)
overlay, _ := p.ParseWithArena(a, `{"age": 31, "email": "[email protected]"}`)

merged, changed, err := astjson.MergeValues(a, base, overlay)
fmt.Println(merged) // {"name":"alice","age":31,"email":"[email protected]"}

MergeValuesWithPath wraps b in a nested object at the given path before merging:

extra, _ := p.ParseWithArena(a, `"1.0"`)
merged, _, _ = astjson.MergeValuesWithPath(a, base, extra, "metadata", "version")
// equivalent to merging {"metadata":{"version":"1.0"}} into base
Iterating objects
v, _ := p.Parse(`{"a": 1, "b": "two", "c": [3]}`)
o, _ := v.Object()
o.Visit(func(key []byte, val *astjson.Value) {
    fmt.Printf("%s: %s\n", key, val)
})
// a: 1
// b: "two"
// c: [3]

Object keys are visited in their original order. The key slice and val pointer must not be retained after the callback returns.

Scanning a stream of JSON values
var sc astjson.Scanner
sc.Init(`{"foo":"bar"} [1,2] "hello" true 42`)
for sc.Next() {
    fmt.Printf("%s\n", sc.Value())
}
if err := sc.Error(); err != nil {
    log.Fatal(err)
}
Serialization
// Serialize to a new byte slice:
data := v.MarshalTo(nil)

// Append to an existing buffer:
buf = v.MarshalTo(buf)

// Get a string representation:
s := v.String()
DeepCopy

DeepCopy creates a complete copy of a value tree on the given arena. This is the safe way to insert heap-allocated values into arena-allocated containers:

a := arena.NewMonotonicArena()
obj := astjson.ObjectValue(a)

heapVal, _ := astjson.Parse(`{"nested": "data"}`)
obj.Set(a, "key", astjson.DeepCopy(a, heapVal))  // safe: copy lives in arena

When a is nil, DeepCopy returns the value unchanged (no-op in heap mode).

GC & Arena Safety

This section is critical reading if you use arena mode. Misuse leads to silent use-after-free bugs that are difficult to diagnose.

Heap mode vs arena mode
Heap mode (nil arena) Arena mode (non-nil arena)
Allocation Standard Go heap Monotonic arena bump allocator
GC tracking All pointers traced normally Arena memory is invisible to GC
Lifetime Until GC collects (no live refs) Until the arena is dropped/reset
When to use Simple cases, low throughput High throughput, request-scoped work
The noscan invariant

Arena buffers are allocated as []byte slabs. Go's GC treats []byte as containing no pointers — it never scans inside them. This means any pointer stored in arena memory is invisible to the GC. This includes:

  • Value.a ([]*Value) — array element pointers
  • Object.kvs ([]*kv) — key-value entry pointers
  • kv.v (*Value) — value pointers inside object entries
  • Value.s / kv.k (string) — string data pointers

The library maintains safety by copying all string data onto the arena when a non-nil arena is provided. But pointer fields to Value structs are the caller's responsibility when inserting values across allocation boundaries.

The golden rule

Never store a heap-allocated *Value into an arena-allocated container unless another GC-visible reference keeps it alive.

If the only reference to a heap Value lives in arena memory, the GC cannot see it and may collect it, causing a use-after-free. Use DeepCopy to copy the value onto the arena first.

Unsafe:

arenaObj := astjson.ObjectValue(a)            // arena-allocated
heapVal := astjson.StringValue(nil, "hello")  // heap-allocated
arenaObj.Set(a, "key", heapVal)               // UNSAFE: GC can't see this ref
heapVal = nil                                 // GC may collect it

Safe — use DeepCopy:

arenaObj := astjson.ObjectValue(a)
heapVal := astjson.StringValue(nil, "hello")
arenaObj.Set(a, "key", astjson.DeepCopy(a, heapVal))  // safe: copy lives in arena

Also safe — all values from the same arena:

arenaObj := astjson.ObjectValue(a)
arenaVal := astjson.StringValue(a, "hello")
arenaObj.Set(a, "key", arenaVal)  // safe: both on same arena
Affected APIs

These APIs store value pointers directly. When the container is arena-allocated, the value must also be arena-allocated (same arena) or a package-level singleton:

  • Object.Set / Value.Set — stores the value argument directly
  • Value.SetArrayItem — stores the value argument directly
  • AppendToArray / Value.AppendArrayItems — stores elements directly
  • MergeValues / MergeValuesWithPath — may store values from b into a

Package-level singletons (NullValue, and the internal valueTrue, valueFalse, valueNull) are always safe because they are GC-visible global variables.

Arena lifetime

Keep the arena alive for as long as any values allocated on it are in use. In performance-sensitive code where the compiler might not prove liveness, use runtime.KeepAlive:

a := arena.NewMonotonicArena()
v, _ := p.ParseWithArena(a, input)

// ... use v ...
result := v.MarshalTo(nil)

runtime.KeepAlive(a) // ensure arena outlives all uses of v
Don't mix arenas

All values in a single tree should come from the same arena. If MergeValues returns a value from b and a/b were allocated on different arenas, resetting one arena while the other is still in use causes silent corruption.

Best Practices

  • One arena per unit of work. Create an arena at the start of a request, parse and build values on it, serialize the result, then let the arena be collected. This gives you a clear, bounded lifetime.
  • Use DeepCopy at boundaries. When inserting a value from an unknown source (different arena, heap, parsed separately) into an arena container, wrap it in DeepCopy(a, val). This is a no-op when a is nil.
  • Prefer arena mode for hot paths. Arena mode avoids per-Value heap allocations, reducing GC pause time in high-throughput services.
  • Use heap mode for simplicity. If GC pressure is not a concern, pass nil for the arena. The GC handles all lifetimes and there are no mixing pitfalls.
  • Don't share values across goroutines. Parser, Value, Object, and Scanner are not safe for concurrent use. Use per-goroutine instances.
  • Use runtime.KeepAlive(arena) when in doubt. If there is any chance the compiler could determine the arena is unused before you finish reading values from it, add a KeepAlive call after the last use.
  • Deduplicate keys if merging from untrusted sources. DeduplicateObjectKeysRecursively(v) removes duplicate object keys in place, keeping the first occurrence.

Security

  • astjson shouldn't crash or panic when parsing input strings specially crafted by an attacker. It must return error on invalid input JSON.
  • astjson requires up to sizeof(Value) * len(inputJSON) bytes of memory for parsing inputJSON string. Limit the maximum size of the inputJSON before parsing it in order to limit the maximum memory usage.

Performance optimization tips

  • Prefer calling Value.Get* on the value returned from Parser instead of calling Get* one-liners when multiple fields must be obtained from JSON, since each Get* one-liner re-parses the input JSON again.
  • Prefer calling once Value.Get for common prefix paths and then calling Value.Get* on the returned value for distinct suffix paths.
  • Prefer iterating over the array returned from Value.GetArray with a range loop instead of calling Value.Get* for each array item.

Fuzzing

Install go-fuzz & optionally the go-fuzz-corpus.

go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build

Build using go-fuzz-build and run go-fuzz with an optional corpus.

mkdir -p workdir/corpus
cp $GOPATH/src/github.com/dvyukov/go-fuzz-corpus/json/corpus/* workdir/corpus
go-fuzz-build github.com/wundergraph/astjson
go-fuzz -bin=astjson-fuzz.zip -workdir=workdir

Benchmarks

Go 1.12 has been used for benchmarking.

Legend:

  • small - parse small.json (190 bytes).

  • medium - parse medium.json (2.3KB).

  • large - parse large.json (28KB).

  • canada - parse canada.json (2.2MB).

  • citm - parse citm_catalog.json (1.7MB).

  • twitter - parse twitter.json (617KB).

  • stdjson-map - parse into a map[string]interface{} using encoding/json.

  • stdjson-struct - parse into a struct containing a subset of fields of the parsed JSON, using encoding/json.

  • stdjson-empty-struct - parse into an empty struct using encoding/json. This is the fastest possible solution for encoding/json, may be used for json validation. See also benchmark results for json validation.

  • fastjson - parse using fastjson without fields access.

  • fastjson-get - parse using fastjson with fields access similar to stdjson-struct.

$ GOMAXPROCS=1 go test github.com/wundergraph/astjson -bench='Parse$'
goos: linux
goarch: amd64
pkg: github.com/wundergraph/astjson
BenchmarkParse/small/stdjson-map         	  200000	      7305 ns/op	  26.01 MB/s	     960 B/op	      51 allocs/op
BenchmarkParse/small/stdjson-struct      	  500000	      3431 ns/op	  55.37 MB/s	     224 B/op	       4 allocs/op
BenchmarkParse/small/stdjson-empty-struct         	  500000	      2273 ns/op	  83.58 MB/s	     168 B/op	       2 allocs/op
BenchmarkParse/small/fastjson                     	 5000000	       347 ns/op	 547.53 MB/s	       0 B/op	       0 allocs/op
BenchmarkParse/small/fastjson-get                 	 2000000	       620 ns/op	 306.39 MB/s	       0 B/op	       0 allocs/op
BenchmarkParse/medium/stdjson-map                 	   30000	     40672 ns/op	  57.26 MB/s	   10196 B/op	     208 allocs/op
BenchmarkParse/medium/stdjson-struct              	   30000	     47792 ns/op	  48.73 MB/s	    9174 B/op	     258 allocs/op
BenchmarkParse/medium/stdjson-empty-struct        	  100000	     22096 ns/op	 105.40 MB/s	     280 B/op	       5 allocs/op
BenchmarkParse/medium/fastjson                    	  500000	      3025 ns/op	 769.90 MB/s	       0 B/op	       0 allocs/op
BenchmarkParse/medium/fastjson-get                	  500000	      3211 ns/op	 725.20 MB/s	       0 B/op	       0 allocs/op
BenchmarkParse/large/stdjson-map                  	    2000	    614079 ns/op	  45.79 MB/s	  210734 B/op	    2785 allocs/op
BenchmarkParse/large/stdjson-struct               	    5000	    298554 ns/op	  94.18 MB/s	   15616 B/op	     353 allocs/op
BenchmarkParse/large/stdjson-empty-struct         	    5000	    268577 ns/op	 104.69 MB/s	     280 B/op	       5 allocs/op
BenchmarkParse/large/fastjson                     	   50000	     35210 ns/op	 798.56 MB/s	       5 B/op	       0 allocs/op
BenchmarkParse/large/fastjson-get                 	   50000	     35171 ns/op	 799.46 MB/s	       5 B/op	       0 allocs/op
BenchmarkParse/canada/stdjson-map                 	      20	  68147307 ns/op	  33.03 MB/s	12260502 B/op	  392539 allocs/op
BenchmarkParse/canada/stdjson-struct              	      20	  68044518 ns/op	  33.08 MB/s	12260123 B/op	  392534 allocs/op
BenchmarkParse/canada/stdjson-empty-struct        	     100	  17709250 ns/op	 127.11 MB/s	     280 B/op	       5 allocs/op
BenchmarkParse/canada/fastjson                    	     300	   4182404 ns/op	 538.22 MB/s	  254902 B/op	     381 allocs/op
BenchmarkParse/canada/fastjson-get                	     300	   4274744 ns/op	 526.60 MB/s	  254902 B/op	     381 allocs/op
BenchmarkParse/citm/stdjson-map                   	      50	  27772612 ns/op	  62.19 MB/s	 5214163 B/op	   95402 allocs/op
BenchmarkParse/citm/stdjson-struct                	     100	  14936191 ns/op	 115.64 MB/s	    1989 B/op	      75 allocs/op
BenchmarkParse/citm/stdjson-empty-struct          	     100	  14946034 ns/op	 115.56 MB/s	     280 B/op	       5 allocs/op
BenchmarkParse/citm/fastjson                      	    1000	   1879714 ns/op	 918.87 MB/s	   17628 B/op	      30 allocs/op
BenchmarkParse/citm/fastjson-get                  	    1000	   1881598 ns/op	 917.94 MB/s	   17628 B/op	      30 allocs/op
BenchmarkParse/twitter/stdjson-map                	     100	  11289146 ns/op	  55.94 MB/s	 2187878 B/op	   31266 allocs/op
BenchmarkParse/twitter/stdjson-struct             	     300	   5779442 ns/op	 109.27 MB/s	     408 B/op	       6 allocs/op
BenchmarkParse/twitter/stdjson-empty-struct       	     300	   5738504 ns/op	 110.05 MB/s	     408 B/op	       6 allocs/op
BenchmarkParse/twitter/fastjson                   	    2000	    774042 ns/op	 815.86 MB/s	    2541 B/op	       2 allocs/op
BenchmarkParse/twitter/fastjson-get               	    2000	    777833 ns/op	 811.89 MB/s	    2541 B/op	       2 allocs/op

Benchmark results for json validation:

$ GOMAXPROCS=1 go test github.com/wundergraph/astjson -bench='Validate$'
goos: linux
goarch: amd64
pkg: github.com/wundergraph/astjson
BenchmarkValidate/small/stdjson 	 2000000	       955 ns/op	 198.83 MB/s	      72 B/op	       2 allocs/op
BenchmarkValidate/small/fastjson         	 5000000	       384 ns/op	 493.60 MB/s	       0 B/op	       0 allocs/op
BenchmarkValidate/medium/stdjson         	  200000	     10799 ns/op	 215.66 MB/s	     184 B/op	       5 allocs/op
BenchmarkValidate/medium/fastjson        	  300000	      3809 ns/op	 611.30 MB/s	       0 B/op	       0 allocs/op
BenchmarkValidate/large/stdjson          	   10000	    133064 ns/op	 211.31 MB/s	     184 B/op	       5 allocs/op
BenchmarkValidate/large/fastjson         	   30000	     45268 ns/op	 621.14 MB/s	       0 B/op	       0 allocs/op
BenchmarkValidate/canada/stdjson         	     200	   8470904 ns/op	 265.74 MB/s	     184 B/op	       5 allocs/op
BenchmarkValidate/canada/fastjson        	     500	   2973377 ns/op	 757.07 MB/s	       0 B/op	       0 allocs/op
BenchmarkValidate/citm/stdjson           	     200	   7273172 ns/op	 237.48 MB/s	     184 B/op	       5 allocs/op
BenchmarkValidate/citm/fastjson          	    1000	   1684430 ns/op	1025.39 MB/s	       0 B/op	       0 allocs/op
BenchmarkValidate/twitter/stdjson        	     500	   2849439 ns/op	 221.63 MB/s	     312 B/op	       6 allocs/op
BenchmarkValidate/twitter/fastjson       	    2000	   1036796 ns/op	 609.10 MB/s	       0 B/op	       0 allocs/op

FAQ

  • Q: There are a ton of other high-perf packages for JSON parsing in Go. Why creating yet another package? A: Because other packages require either rigid JSON schema via struct magic and code generation or perform poorly when multiple unrelated fields must be obtained from the parsed JSON. Additionally, astjson provides nicer API.

  • Q: What is the main purpose for astjson? A: High-perf JSON parsing for RTB and other JSON-RPC services.

  • Q: Why doesn't astjson provide fast marshaling (serialization)? A: It provides Value.MarshalTo for serializing parsed/constructed JSON trees. For high-performance templated JSON marshaling, consider quicktemplate.

  • Q: astjson crashes my program! A: There is high probability of improper use.

    • Make sure you don't hold references to objects recursively returned by Parser / Scanner beyond the next Parser.Parse / Scanner.Next call.
    • Make sure you don't access astjson objects from concurrently running goroutines.
    • If using arena mode, read the GC & Arena Safety section carefully. Mixing heap and arena values without DeepCopy causes silent use-after-free.
    • Build and run your program with -race flag. Make sure the race detector detects zero races.
    • If your program continues crashing after fixing the issues above, file a bug.

Documentation

Overview

Package astjson provides fast JSON parsing with optional arena allocation.

Arbitrary JSON may be parsed without creating structs or generating Go code. Just parse JSON and get the required fields with Get* functions.

Heap Mode vs Arena Mode

The library operates in two memory modes depending on whether an arena.Arena is provided:

Heap mode (nil arena): All Value structs and their backing data are allocated on the Go heap. The garbage collector tracks all references normally. Use Parse, ParseBytes, or Parser.Parse / Parser.ParseBytes for this mode. Parsed values remain valid until the next call to any Parse method on the same Parser, or indefinitely when using the package-level convenience functions.

Arena mode (non-nil arena): All Value structs, string backing bytes, and slice backing arrays are allocated on the arena. The Go GC does not scan arena memory, so the library copies all input data onto the arena before parsing. This means the caller can drop references to the input string/bytes immediately after parsing. Parsed values remain valid for the lifetime of the arena. Use ParseWithArena, ParseBytesWithArena, or Parser.ParseWithArena / Parser.ParseBytesWithArena for this mode.

Arena mode is useful for high-throughput parsing where GC pressure from millions of small Value allocations would degrade performance. Instead of creating heap garbage, all allocations go into a contiguous arena buffer that can be reset and reused.

GC Safety Invariant

When using arena mode, the following invariant is maintained automatically:

Arena-allocated Values never reference heap-allocated string data.

This is critical because the GC does not scan arena memory for pointers. If an arena-allocated Value.s field pointed to a heap string's backing array, the GC could collect that string (since it cannot see the reference in arena memory), causing a use-after-free. The library prevents this by:

  • Copying the entire input string/bytes onto the arena before parsing, so all parsed substrings (number literals, string values, object keys) point into arena memory.
  • Copying string arguments onto the arena in value constructors like StringValue, IntValue, etc.
  • Copying object keys onto the arena in Object.Set.

When using heap mode (nil arena), all Values live on the heap where the GC can see them, so heap string references are safe.

Mixing Arena and Heap Values

The noscan property extends beyond string data to all pointer fields within arena-allocated structs. Because arena buffers are raw []byte allocations, the GC does not trace pointer fields such as Value.a ([]*Value), Object.kvs ([]*kv), or kv.v (*Value) that live inside arena memory.

This means storing a heap-allocated *Value into an arena-allocated container is unsafe if no other GC-visible reference to that heap Value exists. The GC may collect the heap Value since it cannot see the reference in arena memory, resulting in a use-after-free.

Affected APIs (when the container is arena-allocated and the value is heap-allocated):

Safe patterns:

  • All values from a single arena: always safe.
  • All values on the heap (nil arena): always safe.
  • Package-level singletons (valueTrue, valueFalse, valueNull, NullValue): always safe because they are GC-visible global variables.
  • Using DeepCopy to copy a heap value onto the arena before storing it: always safe because the copy lives entirely in arena memory.

Unsafe pattern:

arenaObj := ObjectValue(a)            // arena-allocated
heapVal := StringValue(nil, "hello")  // heap-allocated
arenaObj.Set(a, "key", heapVal)       // UNSAFE if heapVal has no other reference
heapVal = nil                         // GC may now collect the heap Value

Safe pattern using DeepCopy:

arenaObj := ObjectValue(a)
heapVal := StringValue(nil, "hello")  // heap-allocated
arenaObj.Set(a, "key", DeepCopy(a, heapVal))  // safe: copy lives in a
heapVal = nil                                  // GC collects original, copy is in a

Value Constructors

Use StringValue, IntValue, FloatValue, NumberValue, TrueValue, FalseValue, ObjectValue, and ArrayValue to create new values. These accept an arena.Arena parameter: pass nil for heap allocation or a non-nil arena for arena allocation. All string data is automatically copied onto the arena when a non-nil arena is provided.

Concurrency

Parser, Value, Object, and Scanner cannot be used from concurrent goroutines. Use per-goroutine instances.

Index

Examples

Constants

View Source
const MaxDepth = 300

MaxDepth is the maximum depth for nested JSON.

Variables

View Source
var (
	// ErrMergeDifferentTypes is returned when merging two values of incompatible types.
	ErrMergeDifferentTypes = errors.New("cannot merge different types")
	// ErrMergeDifferingArrayLengths is returned when merging arrays of different lengths.
	ErrMergeDifferingArrayLengths = errors.New("cannot merge arrays of differing lengths")
	// ErrMergeUnknownType is returned when merging a value with an unrecognized type.
	ErrMergeUnknownType = errors.New("cannot merge unknown type")
)
View Source
var (
	NullValue = MustParse(`null`)
)

NullValue is a heap-allocated JSON null singleton. It is safe to use from any context (heap or arena) since it is a package-level global that is always reachable by the GC.

Functions

func AppendToArray

func AppendToArray(a arena.Arena, array, value *Value)

AppendToArray appends value to the end of array. Does nothing if array is not of TypeArray. The arena a is used to grow the array's backing slice.

GC safety: when array is arena-allocated (a is non-nil), value must also be arena-allocated from the same arena, or be a package-level singleton. Use DeepCopy on value before calling if value is heap-allocated. See the package documentation section "Mixing Arena and Heap Values".

func DeduplicateObjectKeysRecursively

func DeduplicateObjectKeysRecursively(v *Value)

DeduplicateObjectKeysRecursively removes duplicate object keys from v and all nested objects and arrays, keeping the first occurrence of each key. This modifies v in place and does not require an arena.

func Exists

func Exists(data []byte, keys ...string) bool

Exists returns true if the field identified by keys path exists in JSON data.

Array indexes may be represented as decimal numbers in keys.

False is returned on error. Use Parser for proper error handling.

Parser is faster when multiple fields must be checked in the JSON.

Example
package main

import (
	"fmt"

	"github.com/wundergraph/astjson"
)

func main() {
	data := []byte(`{"foo": [1.23,{"bar":33,"baz":null}]}`)

	fmt.Printf("exists(data.foo) = %v\n", astjson.Exists(data, "foo"))
	fmt.Printf("exists(data.foo[0]) = %v\n", astjson.Exists(data, "foo", "0"))
	fmt.Printf("exists(data.foo[1].baz) = %v\n", astjson.Exists(data, "foo", "1", "baz"))
	fmt.Printf("exists(data.foobar) = %v\n", astjson.Exists(data, "foobar"))
	fmt.Printf("exists(data.foo.bar) = %v\n", astjson.Exists(data, "foo", "bar"))

}
Output:

exists(data.foo) = true
exists(data.foo[0]) = true
exists(data.foo[1].baz) = true
exists(data.foobar) = false
exists(data.foo.bar) = false

func GetBool

func GetBool(data []byte, keys ...string) bool

GetBool returns boolean value for the field identified by keys path in JSON data.

Array indexes may be represented as decimal numbers in keys.

False is returned on error. Use Parser for proper error handling.

Parser is faster for obtaining multiple fields from JSON.

func GetBytes

func GetBytes(data []byte, keys ...string) []byte

GetBytes returns string value for the field identified by keys path in JSON data.

Array indexes may be represented as decimal numbers in keys.

nil is returned on error. Use Parser for proper error handling.

Parser is faster for obtaining multiple fields from JSON.

func GetFloat64

func GetFloat64(data []byte, keys ...string) float64

GetFloat64 returns float64 value for the field identified by keys path in JSON data.

Array indexes may be represented as decimal numbers in keys.

0 is returned on error. Use Parser for proper error handling.

Parser is faster for obtaining multiple fields from JSON.

func GetInt

func GetInt(data []byte, keys ...string) int

GetInt returns int value for the field identified by keys path in JSON data.

Array indexes may be represented as decimal numbers in keys.

0 is returned on error. Use Parser for proper error handling.

Parser is faster for obtaining multiple fields from JSON.

Example
package main

import (
	"fmt"

	"github.com/wundergraph/astjson"
)

func main() {
	data := []byte(`{"foo": [233,true, {"bar": [2343]} ]}`)

	n1 := astjson.GetInt(data, "foo", "0")
	fmt.Printf("data.foo[0] = %d\n", n1)

	n2 := astjson.GetInt(data, "foo", "2", "bar", "0")
	fmt.Printf("data.foo[2].bar[0] = %d\n", n2)

}
Output:

data.foo[0] = 233
data.foo[2].bar[0] = 2343

func GetString

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

GetString returns string value for the field identified by keys path in JSON data.

Array indexes may be represented as decimal numbers in keys.

An empty string is returned on error. Use Parser for proper error handling.

Parser is faster for obtaining multiple fields from JSON.

Example
package main

import (
	"fmt"

	"github.com/wundergraph/astjson"
)

func main() {
	data := []byte(`{"foo":{"bar":[123,"baz"]}}`)

	s := astjson.GetString(data, "foo", "bar", "1")
	fmt.Printf("data.foo.bar[1] = %s", s)

}
Output:

data.foo.bar[1] = baz

func SetNull

func SetNull(a arena.Arena, v *Value, path ...string)

SetNull sets a null value at the nested key path within v. The null Value is allocated on the arena when a is non-nil. See SetValue for path behavior.

func SetValue

func SetValue(a arena.Arena, v *Value, value *Value, path ...string)

SetValue sets value at the nested key path within v. Intermediate object nodes are created on the arena as needed when they don't exist. The path must have at least one element.

Object keys created along the path are copied onto the arena when a is non-nil, ensuring GC safety. The same arena/heap mixing rules as Object.Set apply to the value argument.

func Validate

func Validate(s string) error

Validate validates JSON s.

func ValidateBytes

func ValidateBytes(b []byte) error

ValidateBytes validates JSON b.

func ValueIsNonNull

func ValueIsNonNull(v *Value) bool

ValueIsNonNull reports whether v is non-nil and not TypeNull.

func ValueIsNull

func ValueIsNull(v *Value) bool

ValueIsNull reports whether v is nil or TypeNull.

Types

type Object

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

Object represents JSON object.

Object cannot be used from concurrent goroutines. Use per-goroutine parsers or ParserPool instead.

Cache-friendly layout: hot data first

func (*Object) Del

func (o *Object) Del(key string)

Del deletes the entry with the given key from o.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	v := astjson.MustParse(`{"foo": 123, "bar": [1,2], "baz": "xyz"}`)
	o, err := v.Object()
	if err != nil {
		log.Fatalf("cannot otain object: %s", err)
	}
	fmt.Printf("%s\n", o)

	o.Del("bar")
	fmt.Printf("%s\n", o)

	o.Del("foo")
	fmt.Printf("%s\n", o)

	o.Del("baz")
	fmt.Printf("%s\n", o)

}
Output:

{"foo":123,"bar":[1,2],"baz":"xyz"}
{"foo":123,"baz":"xyz"}
{"baz":"xyz"}
{}

func (*Object) Get

func (o *Object) Get(key string) *Value

Get returns the value for the given key in the o.

Returns nil if the value for the given key isn't found.

The returned value is valid until Parse is called on the Parser returned o.

func (*Object) Len

func (o *Object) Len() int

Len returns the number of items in the o.

func (*Object) MarshalTo

func (o *Object) MarshalTo(dst []byte) []byte

MarshalTo appends marshaled o to dst and returns the result.

func (*Object) Set

func (o *Object) Set(a arena.Arena, key string, value *Value)

Set sets (key, value) entry in the o.

The value must be unchanged during o lifetime.

GC safety: when o is arena-allocated (a is non-nil), value must also be arena-allocated from the same arena, or be a package-level singleton. Storing a heap-allocated *Value in arena memory is unsafe because the GC does not trace pointers within arena buffers. Use DeepCopy to copy a heap-allocated value onto the arena before passing it here. See the package documentation section "Mixing Arena and Heap Values" for details.

func (*Object) String

func (o *Object) String() string

String returns string representation for the o.

This function is for debugging purposes only. It isn't optimized for speed. See MarshalTo instead.

func (*Object) Visit

func (o *Object) Visit(f func(key []byte, v *Value))

Visit calls f for each item in the o in the original order of the parsed JSON.

f cannot hold key and/or v after returning.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	s := `{
		"obj": { "foo": 1234 },
		"arr": [ 23,4, "bar" ],
		"str": "foobar"
	}`

	var p astjson.Parser
	v, err := p.Parse(s)
	if err != nil {
		log.Fatalf("cannot parse json: %s", err)
	}
	o, err := v.Object()
	if err != nil {
		log.Fatalf("cannot obtain object from json value: %s", err)
	}

	o.Visit(func(k []byte, v *astjson.Value) {
		switch string(k) {
		case "obj":
			fmt.Printf("object %s\n", v)
		case "arr":
			fmt.Printf("array %s\n", v)
		case "str":
			fmt.Printf("string %s\n", v)
		}
	})

}
Output:

object {"foo":1234}
array [23,4,"bar"]
string "foobar"

type ParseError

type ParseError struct {
	Err error
}

ParseError wraps a JSON parsing error.

func NewParseError

func NewParseError(err error) *ParseError

NewParseError wraps err in a ParseError. Returns nil if err is nil.

func (*ParseError) Error

func (p *ParseError) Error() string

Error returns the error message. Returns an empty string if p is nil.

type Parser

type Parser struct {
}

Parser parses JSON.

Parser may be re-used for subsequent parsing.

Parser supports two allocation modes: heap mode (Parse, ParseBytes) where all values are heap-allocated and GC-managed, and arena mode (ParseWithArena, ParseBytesWithArena) where all values and their backing data are allocated on a caller-provided arena. See the package documentation for details on GC safety.

Parser cannot be used from concurrent goroutines. Use per-goroutine parsers or ParserPool instead.

func (*Parser) Parse

func (p *Parser) Parse(s string) (*Value, error)

Parse parses s containing JSON.

The returned value is valid until the next call to Parse*.

Use Scanner if a stream of JSON values must be parsed.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	var p astjson.Parser
	v, err := p.Parse(`{"foo":"bar", "baz": 123}`)
	if err != nil {
		log.Fatalf("cannot parse json: %s", err)
	}

	fmt.Printf("foo=%s, baz=%d", v.GetStringBytes("foo"), v.GetInt("baz"))

}
Output:

foo=bar, baz=123
Example (Reuse)
package main

import (
	"fmt"
	"log"
	"strconv"

	"github.com/wundergraph/astjson"
)

func main() {
	var p astjson.Parser

	// p may be re-used for parsing multiple json strings.
	// This improves parsing speed by reducing the number
	// of memory allocations.
	//
	// Parse call invalidates all the objects previously obtained from p,
	// so don't hold these objects after parsing the next json.

	for i := 0; i < 3; i++ {
		s := fmt.Sprintf(`["foo_%d","bar_%d","%d"]`, i, i, i)
		v, err := p.Parse(s)
		if err != nil {
			log.Fatalf("cannot parse json: %s", err)
		}
		key := strconv.Itoa(i)
		fmt.Printf("a[%d]=%s\n", i, v.GetStringBytes(key))
	}

}
Output:

a[0]=foo_0
a[1]=bar_1
a[2]=2

func (*Parser) ParseBytes

func (p *Parser) ParseBytes(b []byte) (*Value, error)

ParseBytes parses b containing JSON.

The returned Value is valid until the next call to Parse*.

Use Scanner if a stream of JSON values must be parsed.

func (*Parser) ParseBytesWithArena

func (p *Parser) ParseBytesWithArena(a arena.Arena, b []byte) (*Value, error)

ParseBytesWithArena parses b containing JSON, allocating all values on the arena.

The input bytes b are copied onto the arena before parsing, so the caller may reuse or discard b immediately after this call returns. All parsed Values, their string data, object keys, and array backing slices live entirely in arena memory, making the result independent of the GC.

The returned value is valid for the lifetime of the arena.

When a is nil, behaves identically to ParseBytes (heap allocation). In that case the caller must not modify b while the returned Value is in use, as it may reference b's underlying memory via zero-copy conversion.

func (*Parser) ParseWithArena

func (p *Parser) ParseWithArena(a arena.Arena, s string) (*Value, error)

ParseWithArena parses s containing JSON, allocating all values on the arena.

The input string s is copied onto the arena before parsing, so the caller may drop references to s immediately after this call returns. All parsed Values, their string data, object keys, and array backing slices live entirely in arena memory, making the result independent of the GC.

The returned value is valid for the lifetime of the arena.

When a is nil, behaves identically to Parse (heap allocation).

type Scanner

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

Scanner scans a series of JSON values. Values may be delimited by whitespace.

Scanner may parse JSON lines ( http://jsonlines.org/ ).

Scanner may be re-used for subsequent parsing.

Scanner cannot be used from concurrent goroutines.

Use Parser for parsing only a single JSON value.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	var sc astjson.Scanner

	sc.Init(`   {"foo":  "bar"  }[  ]
		12345"xyz" true false null    `)

	for sc.Next() {
		fmt.Printf("%s\n", sc.Value())
	}
	if err := sc.Error(); err != nil {
		log.Fatalf("unexpected error: %s", err)
	}

}
Output:

{"foo":"bar"}
[]
12345
"xyz"
true
false
null
Example (Reuse)
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	var sc astjson.Scanner

	// The sc may be re-used in order to reduce the number
	// of memory allocations.
	for i := 0; i < 3; i++ {
		s := fmt.Sprintf(`[%d] "%d"`, i, i)
		sc.Init(s)
		for sc.Next() {
			fmt.Printf("%s,", sc.Value())
		}
		if err := sc.Error(); err != nil {
			log.Fatalf("unexpected error: %s", err)
		}
		fmt.Printf("\n")
	}

}
Output:

[0],"0",
[1],"1",
[2],"2",

func (*Scanner) Error

func (sc *Scanner) Error() error

Error returns the last error.

func (*Scanner) Init

func (sc *Scanner) Init(s string)

Init initializes sc with the given s.

s may contain multiple JSON values, which may be delimited by whitespace.

func (*Scanner) InitBytes

func (sc *Scanner) InitBytes(b []byte)

InitBytes initializes sc with the given b.

b may contain multiple JSON values, which may be delimited by whitespace.

func (*Scanner) Next

func (sc *Scanner) Next() bool

Next parses the next JSON value from s passed to Init.

Returns true on success. The parsed value is available via Value call.

Returns false either on error or on the end of s. Call Error in order to determine the cause of the returned false.

func (*Scanner) Value

func (sc *Scanner) Value() *Value

Value returns the last parsed value.

The value is valid until the Next call.

type Type

type Type int

Type represents JSON type.

const (
	// TypeNull is JSON null.
	TypeNull Type = 0

	// TypeObject is JSON object type.
	TypeObject Type = 1

	// TypeArray is JSON array type.
	TypeArray Type = 2

	// TypeString is JSON string type.
	TypeString Type = 3

	// TypeNumber is JSON number type.
	TypeNumber Type = 4

	// TypeTrue is JSON true.
	TypeTrue Type = 5

	// TypeFalse is JSON false.
	TypeFalse Type = 6
)

func (Type) String

func (t Type) String() string

String returns string representation of t.

type Value

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

Value represents any JSON value.

Call Type in order to determine the actual type of the JSON value.

Value cannot be used from concurrent goroutines. Use per-goroutine parsers or ParserPool instead.

Cache-friendly layout: hot data first, compact structure

func ArrayValue

func ArrayValue(a arena.Arena) *Value

ArrayValue creates an empty JSON array Value.

Use Value.SetArrayItem or AppendToArray to add elements. The array's backing slice is grown on the arena when a is non-nil.

When a is nil, the Value is heap-allocated.

func DeepCopy added in v1.1.0

func DeepCopy(a arena.Arena, v *Value) *Value

DeepCopy returns a deep copy of v allocated entirely on arena a. All string data, slice backing arrays, child Values, and object keys are arena-allocated, making the result self-contained within a.

Use DeepCopy when inserting a heap-parsed *Value into an arena-allocated container (via Object.Set, Value.SetArrayItem, [AppendArrayItems], etc.) to prevent the GC from collecting the value while the arena container still references it. Example:

heapVal, _ := Parse(`"hello"`)                       // heap-allocated
arenaObj.Set(a, "key", DeepCopy(a, heapVal))         // safe: copy lives in a

When a is nil (heap mode), DeepCopy returns v unchanged. In heap mode the GC traces all references normally, so no copy is needed.

func FalseValue

func FalseValue(a arena.Arena) *Value

FalseValue creates a JSON false Value.

When a is non-nil, the Value struct is allocated on the arena. When a is nil, it is heap-allocated.

func FloatValue

func FloatValue(a arena.Arena, f float64) *Value

FloatValue creates a JSON number Value from a float64.

When a is non-nil, both the Value struct and the number's string representation are allocated on the arena. When a is nil, the Value is heap-allocated normally.

func IntValue

func IntValue(a arena.Arena, i int) *Value

IntValue creates a JSON number Value from an int.

When a is non-nil, both the Value struct and the number's string representation are allocated on the arena. When a is nil, the Value is heap-allocated normally.

func MergeValues

func MergeValues(ar arena.Arena, a, b *Value) (v *Value, changed bool, err error)

MergeValues recursively merges b into a and returns the result. For objects, keys from b are added to or replace keys in a. For arrays, elements are merged pairwise (arrays must have equal length). For scalars, b replaces a when the values differ.

The arena ar is used for any new allocations during the merge (new object entries, key copies). Both a and b should have been allocated using the same arena (or both on the heap) to avoid mixing memory lifetimes.

Returns the merged value, whether a was changed, and any error. If a is nil, returns (b, true, nil). If b is nil, returns (a, false, nil).

func MergeValuesWithPath

func MergeValuesWithPath(ar arena.Arena, a, b *Value, path ...string) (v *Value, changed bool, err error)

MergeValuesWithPath wraps b in a nested object structure at the given path, then merges the result into a using MergeValues. For example, with path ["foo", "bar"], b is wrapped as {"foo": {"bar": b}} before merging.

If path is empty, behaves identically to MergeValues.

The arena ar is used for allocating the wrapper objects and during the merge.

func MustParse

func MustParse(s string) *Value

MustParse parses json string s.

The function panics if s cannot be parsed. The function is slower than the Parser.Parse for re-used Parser.

func MustParseBytes

func MustParseBytes(b []byte) *Value

MustParseBytes parses b containing json.

The function panics if b cannot be parsed. The function is slower than the Parser.ParseBytes for re-used Parser.

func NumberValue

func NumberValue(a arena.Arena, s string) *Value

NumberValue creates a JSON number Value from a raw numeric string.

The string s must be a valid JSON number (e.g. "123", "3.14", "1e10"). No validation is performed.

When a is non-nil, both the Value struct and the string's backing bytes are allocated on the arena (the string is copied). When a is nil, the Value is heap-allocated and references s directly.

func ObjectValue

func ObjectValue(a arena.Arena) *Value

ObjectValue creates an empty JSON object Value.

Use Object.Set or Value.Set to add entries. Object keys and entry backing slices are allocated on the arena when a is non-nil.

When a is nil, the Value is heap-allocated.

func Parse

func Parse(s string) (*Value, error)

Parse parses json string s.

The function is slower than the Parser.Parse for re-used Parser.

func ParseBytes

func ParseBytes(b []byte) (*Value, error)

ParseBytes parses b containing json.

The function is slower than the Parser.ParseBytes for re-used Parser.

func ParseBytesWithArena

func ParseBytesWithArena(a arena.Arena, b []byte) (*Value, error)

ParseBytesWithArena parses b containing json, allocating all values on the arena.

The input bytes are copied onto the arena before parsing, so the caller may reuse or discard b immediately after this call returns. All parsed values live entirely in arena memory and are valid for the arena's lifetime.

When a is nil, behaves identically to ParseBytes (heap allocation). In that case the caller must not modify b while the returned Value is in use.

The function is slower than Parser.ParseBytesWithArena for re-used Parser.

func ParseWithArena

func ParseWithArena(a arena.Arena, s string) (*Value, error)

ParseWithArena parses json string s, allocating all values on the arena.

The input string is copied onto the arena before parsing, so the caller may drop references to s immediately after this call returns. All parsed values live entirely in arena memory and are valid for the arena's lifetime.

When a is nil, behaves identically to Parse (heap allocation).

The function is slower than Parser.ParseWithArena for re-used Parser.

func StringValue

func StringValue(a arena.Arena, s string) *Value

StringValue creates a JSON string Value containing s.

When a is non-nil, both the Value struct and the string's backing bytes are allocated on the arena (the string is copied). The caller may drop references to s immediately.

When a is nil, the Value is heap-allocated and references s directly. The caller must keep s reachable for the lifetime of the returned Value.

func StringValueBytes

func StringValueBytes(a arena.Arena, b []byte) *Value

StringValueBytes creates a JSON string Value from a byte slice.

When a is non-nil, both the Value struct and the bytes are copied onto the arena. The caller may reuse or discard b immediately.

When a is nil, the Value is heap-allocated and references b's underlying memory directly via zero-copy conversion. The caller must not modify b and must keep it reachable for the lifetime of the returned Value.

func TrueValue

func TrueValue(a arena.Arena) *Value

TrueValue creates a JSON true Value.

When a is non-nil, the Value struct is allocated on the arena. When a is nil, it is heap-allocated.

func (*Value) AppendArrayItems

func (v *Value) AppendArrayItems(a arena.Arena, right *Value)

AppendArrayItems appends all elements from right into v. Both v and right must be TypeArray; does nothing otherwise. The arena a is used to grow v's backing slice.

GC safety: when v is arena-allocated (a is non-nil), right and its elements must also be arena-allocated from the same arena. Use DeepCopy on right before calling if right is heap-allocated. See the package documentation section "Mixing Arena and Heap Values".

func (*Value) Array

func (v *Value) Array() ([]*Value, error)

Array returns the underlying JSON array for the v.

The returned array is valid until Parse is called on the Parser returned v.

Use GetArray if you don't need error handling.

func (*Value) Bool

func (v *Value) Bool() (bool, error)

Bool returns the underlying JSON bool for the v.

Use GetBool if you don't need error handling.

func (*Value) Del

func (v *Value) Del(key string)

Del deletes the entry with the given key from array or object v.

Example
package main

import (
	"fmt"

	"github.com/wundergraph/astjson"
)

func main() {
	v := astjson.MustParse(`{"foo": 123, "bar": [1,2], "baz": "xyz"}`)
	fmt.Printf("%s\n", v)

	v.Del("foo")
	fmt.Printf("%s\n", v)

	v.Get("bar").Del("0")
	fmt.Printf("%s\n", v)

}
Output:

{"foo":123,"bar":[1,2],"baz":"xyz"}
{"bar":[1,2],"baz":"xyz"}
{"bar":[2],"baz":"xyz"}

func (*Value) Exists

func (v *Value) Exists(keys ...string) bool

Exists returns true if the field exists for the given keys path.

Array indexes may be represented as decimal numbers in keys.

func (*Value) Float64

func (v *Value) Float64() (float64, error)

Float64 returns the underlying JSON number for the v.

Use GetFloat64 if you don't need error handling.

func (*Value) Get

func (v *Value) Get(keys ...string) *Value

Get returns value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

nil is returned for non-existing keys path.

The returned value is valid until Parse is called on the Parser returned v.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	s := `{"foo":[{"bar":{"baz":123,"x":"434"},"y":[]},[null, false]],"qwe":true}`
	var p astjson.Parser
	v, err := p.Parse(s)
	if err != nil {
		log.Fatalf("cannot parse json: %s", err)
	}

	vv := v.Get("foo", "0", "bar", "x")
	fmt.Printf("foo[0].bar.x=%s\n", vv.GetStringBytes())

	vv = v.Get("qwe")
	fmt.Printf("qwe=%v\n", vv.GetBool())

	vv = v.Get("foo", "1")
	fmt.Printf("foo[1]=%s\n", vv)

	vv = v.Get("foo").Get("1").Get("1")
	fmt.Printf("foo[1][1]=%s\n", vv)

	// non-existing key
	vv = v.Get("foo").Get("bar").Get("baz", "1234")
	fmt.Printf("foo.bar.baz[1234]=%v\n", vv)

}
Output:

foo[0].bar.x=434
qwe=true
foo[1]=[null,false]
foo[1][1]=false
foo.bar.baz[1234]=<nil>

func (*Value) GetArray

func (v *Value) GetArray(keys ...string) []*Value

GetArray returns array value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

nil is returned for non-existing keys path or for invalid value type.

The returned array is valid until Parse is called on the Parser returned v.

func (*Value) GetBool

func (v *Value) GetBool(keys ...string) bool

GetBool returns bool value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

false is returned for non-existing keys path or for invalid value type.

func (*Value) GetFloat64

func (v *Value) GetFloat64(keys ...string) float64

GetFloat64 returns float64 value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

0 is returned for non-existing keys path or for invalid value type.

func (*Value) GetInt

func (v *Value) GetInt(keys ...string) int

GetInt returns int value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

0 is returned for non-existing keys path or for invalid value type.

func (*Value) GetInt64

func (v *Value) GetInt64(keys ...string) int64

GetInt64 returns int64 value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

0 is returned for non-existing keys path or for invalid value type.

func (*Value) GetObject

func (v *Value) GetObject(keys ...string) *Object

GetObject returns object value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

nil is returned for non-existing keys path or for invalid value type.

The returned object is valid until Parse is called on the Parser returned v.

func (*Value) GetStringBytes

func (v *Value) GetStringBytes(keys ...string) []byte

GetStringBytes returns string value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

nil is returned for non-existing keys path or for invalid value type.

The returned string is valid until Parse is called on the Parser returned v.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	s := `[
		{"foo": "bar"},
		[123, "baz"]
	]`

	var p astjson.Parser
	v, err := p.Parse(s)
	if err != nil {
		log.Fatalf("cannot parse json: %s", err)
	}
	fmt.Printf("v[0].foo = %q\n", v.GetStringBytes("0", "foo"))
	fmt.Printf("v[1][1] = %q\n", v.GetStringBytes("1", "1"))
	fmt.Printf("v[1][0] = %q\n", v.GetStringBytes("1", "0"))
	fmt.Printf("v.foo.bar.baz = %q\n", v.GetStringBytes("foo", "bar", "baz"))

}
Output:

v[0].foo = "bar"
v[1][1] = "baz"
v[1][0] = ""
v.foo.bar.baz = ""

func (*Value) GetUint

func (v *Value) GetUint(keys ...string) uint

GetUint returns uint value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

0 is returned for non-existing keys path or for invalid value type.

func (*Value) GetUint64

func (v *Value) GetUint64(keys ...string) uint64

GetUint64 returns uint64 value by the given keys path.

Array indexes may be represented as decimal numbers in keys.

0 is returned for non-existing keys path or for invalid value type.

func (*Value) Int

func (v *Value) Int() (int, error)

Int returns the underlying JSON int for the v.

Use GetInt if you don't need error handling.

func (*Value) Int64

func (v *Value) Int64() (int64, error)

Int64 returns the underlying JSON int64 for the v.

Use GetInt64 if you don't need error handling.

func (*Value) MarshalTo

func (v *Value) MarshalTo(dst []byte) []byte

MarshalTo appends marshaled v to dst and returns the result.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	s := `{
		"name": "John",
		"items": [
			{
				"key": "foo",
				"value": 123.456,
				"arr": [1, "foo"]
			},
			{
				"key": "bar",
				"field": [3, 4, 5]
			}
		]
	}`
	var p astjson.Parser
	v, err := p.Parse(s)
	if err != nil {
		log.Fatalf("cannot parse json: %s", err)
	}

	// Marshal items.0 into newly allocated buffer.
	buf := v.Get("items", "0").MarshalTo(nil)
	fmt.Printf("items.0 = %s\n", buf)

	// Re-use buf for marshaling items.1.
	buf = v.Get("items", "1").MarshalTo(buf[:0])
	fmt.Printf("items.1 = %s\n", buf)

}
Output:

items.0 = {"key":"foo","value":123.456,"arr":[1,"foo"]}
items.1 = {"key":"bar","field":[3,4,5]}

func (*Value) Object

func (v *Value) Object() (*Object, error)

Object returns the underlying JSON object for the v.

The returned object is valid until Parse is called on the Parser returned v.

Use GetObject if you don't need error handling.

func (*Value) Set

func (v *Value) Set(a arena.Arena, key string, value *Value)

Set sets (key, value) entry in the array or object v.

The value must be unchanged during v lifetime.

Example
v := astjson.MustParse(`{"foo":1,"bar":[2,3]}`)
a := arena.NewMonotonicArena()
// Replace `foo` value with "xyz"
v.Set(a, "foo", astjson.MustParse(`"xyz"`))
// Add "newv":123
v.Set(a, "newv", astjson.MustParse(`123`))
fmt.Printf("%s\n", v)

// Replace `bar.1` with {"x":"y"}
v.Get("bar").Set(a, "1", astjson.MustParse(`{"x":"y"}`))
// Add `bar.3="qwe"
v.Get("bar").Set(a, "3", astjson.MustParse(`"qwe"`))
fmt.Printf("%s\n", v)
Output:

{"foo":"xyz","bar":[2,3],"newv":123}
{"foo":"xyz","bar":[2,{"x":"y"},null,"qwe"],"newv":123}

func (*Value) SetArrayItem

func (v *Value) SetArrayItem(a arena.Arena, idx int, value *Value)

SetArrayItem sets the value in the array v at idx position.

The value must be unchanged during v lifetime.

GC safety: when v is arena-allocated (a is non-nil), value must also be arena-allocated from the same arena, or be a package-level singleton. Use DeepCopy to copy a heap-allocated value onto the arena before passing it here. See the package documentation section "Mixing Arena and Heap Values".

func (*Value) String

func (v *Value) String() string

String returns string representation of the v.

The function is for debugging purposes only. It isn't optimized for speed. See MarshalTo instead.

Don't confuse this function with StringBytes, which must be called for obtaining the underlying JSON string for the v.

func (*Value) StringBytes

func (v *Value) StringBytes() ([]byte, error)

StringBytes returns the underlying JSON string for the v.

The returned string is valid until Parse is called on the Parser returned v.

Use GetStringBytes if you don't need error handling.

func (*Value) Type

func (v *Value) Type() Type

Type returns the type of the v.

Example
package main

import (
	"fmt"
	"log"

	"github.com/wundergraph/astjson"
)

func main() {
	s := `{
		"object": {},
		"array": [],
		"string": "foobar",
		"number": 123.456,
		"true": true,
		"false": false,
		"null": null
	}`

	var p astjson.Parser
	v, err := p.Parse(s)
	if err != nil {
		log.Fatalf("cannot parse json: %s", err)
	}

	fmt.Printf("%s\n", v.Get("object").Type())
	fmt.Printf("%s\n", v.Get("array").Type())
	fmt.Printf("%s\n", v.Get("string").Type())
	fmt.Printf("%s\n", v.Get("number").Type())
	fmt.Printf("%s\n", v.Get("true").Type())
	fmt.Printf("%s\n", v.Get("false").Type())
	fmt.Printf("%s\n", v.Get("null").Type())

}
Output:

object
array
string
number
true
false
null

func (*Value) Uint

func (v *Value) Uint() (uint, error)

Uint returns the underlying JSON uint for the v.

Use GetInt if you don't need error handling.

func (*Value) Uint64

func (v *Value) Uint64() (uint64, error)

Uint64 returns the underlying JSON uint64 for the v.

Use GetInt64 if you don't need error handling.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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