insaneJSON

package module
v0.1.10 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: BSD-3-Clause Imports: 12 Imported by: 26

README

Insane JSON

Fast, zero-allocation JSON library for Go. Decode, navigate, mutate, and encode JSON without unmarshalling into Go structs. Designed for high-throughput pipelines where performance matters.

Installation

go get github.com/ozontech/insane-json

Quick Start

root, err := insaneJSON.DecodeString(`{"name":"John","age":30}`)
if err != nil {
    panic(err)
}
defer insaneJSON.Release(root)

name := root.Dig("name").AsString()   // "John"
age := root.Dig("age").AsInt()        // 30

root.Dig("age").MutateToInt(31)
root.AddField("active").MutateToBool(true)

output := root.Encode(nil) // []byte: {"name":"John","age":31,"active":true}

Examples

Extracting fields from API response
root, err := insaneJSON.DecodeBytes(responseBody)
if err != nil {
    return err
}
defer insaneJSON.Release(root)

status := root.Dig("response", "status").AsString()
code := root.Dig("response", "code").AsInt()
items := root.Dig("response", "data", "items")

if items.IsArray() {
    for _, item := range items.AsArray() {
        id := item.Dig("id").AsInt()
        name := item.Dig("name").AsString()
        fmt.Printf("id=%d name=%s\n", id, name)
    }
}
Transforming JSON logs
root, err := insaneJSON.DecodeBytes(logLine)
if err != nil {
    return err
}
defer insaneJSON.Release(root)

// add tracing info
root.AddField("trace_id").MutateToString(traceID)
root.AddField("processed_at").MutateToString(time.Now().Format(time.RFC3339))

// remove sensitive data
root.Dig("request", "headers", "Authorization").Suicide()
root.Dig("request", "body", "password").Suicide()

// rename field
root.DigField("level").MutateToField("log_level")

output = root.Encode(output[:0])
Filtering array elements
root, err := insaneJSON.DecodeString(`{"users":[{"name":"Alice","active":true},{"name":"Bob","active":false},{"name":"Carol","active":true}]}`)
if err != nil {
    return err
}
defer insaneJSON.Release(root)

users := root.Dig("users")
for _, user := range users.AsArray() {
    if !user.Dig("active").AsBool() {
        user.Suicide()
    }
}

fmt.Println(root.EncodeToString())
// {"users":[{"name":"Alice","active":true},{"name":"Carol","active":true}]}
High-throughput processing with Root reuse
root := insaneJSON.Spawn()
defer insaneJSON.Release(root)

buf := make([]byte, 0, 4096)

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    if err := root.DecodeBytes(scanner.Bytes()); err != nil {
        continue
    }

    root.AddField("source").MutateToString("pipeline-v2")

    buf = root.Encode(buf[:0])
    writer.Write(buf)
}
Working with nested JSON
root, err := insaneJSON.DecodeString(`{"a":{"b":{"c":"deep"}}}`)
if err != nil {
    return err
}
defer insaneJSON.Release(root)

// Dig traverses nested objects
value := root.Dig("a", "b", "c").AsString() // "deep"

// array elements accessed by string index
root2, _ := insaneJSON.DecodeString(`{"items":["zero","one","two"]}`)
defer insaneJSON.Release(root2)

second := root2.Dig("items", "1").AsString() // "one"
Strict mode with error handling
root, err := insaneJSON.DecodeString(`{"count":"not a number"}`)
if err != nil {
    return err
}
defer insaneJSON.Release(root)

node, err := root.DigStrict("count")
if err != nil {
    return err // insaneJSON.ErrNotFound
}

count, err := node.AsInt()
if err != nil {
    return err // insaneJSON.ErrNotNumber
}
Merging objects
root, _ := insaneJSON.DecodeString(`{"a":"1","b":"2"}`)
defer insaneJSON.Release(root)

patch, _ := root.DecodeStringAdditional(`{"b":"updated","c":"3"}`)

root.MergeWith(patch)
fmt.Println(root.EncodeToString())
// {"a":"1","b":"updated","c":"3"}

API Overview

Decode
Function Description
DecodeString(json) (*Root, error) Decode JSON string, returns Root from pool
DecodeBytes(json) (*Root, error) Decode JSON byte slice, returns Root from pool
Spawn() *Root Get an empty Root from pool
Release(root) Return Root to pool
root.DecodeString(json) error Reuse Root to decode another JSON
root.DecodeBytes(json) error Reuse Root to decode another JSON
root.DecodeStringAdditional(json) (*Node, error) Decode JSON using Root's node pool without clearing
root.DecodeBytesAdditional(json) (*Node, error) Decode JSON using Root's node pool without clearing
Navigate
Function Description
node.Dig(path...) *Node Navigate to nested value. Returns nil if not found. You can also access elements by index. See Working with nested JSON
node.DigStrict(path...) (*StrictNode, error) Same as Dig but returns error if not found
node.AsFields() []*Node Get object field nodes
node.AsArray() []*Node Get array element nodes
node.AsFieldValue() *Node Get value node from field node
node.DigField(path...) *Node Get field node (not value) at path
Read Values
Function Description
node.AsString() string Get string value
node.AsInt() int Get integer value
node.AsInt64() int64 Get int64 value
node.AsUint64() uint64 Get uint64 value
node.AsFloat() float64 Get float64 value
node.AsBool() bool Get bool value
node.AsBytes() []byte Get value as byte slice
node.AsEscapedString() string Get JSON-escaped string value
Type Checks
Function Description
node.IsObject() bool Is value an object?
node.IsArray() bool Is value an array?
node.IsString() bool Is value a string?
node.IsNumber() bool Is value a number?
node.IsTrue() bool Is value true?
node.IsFalse() bool Is value false?
node.IsNull() bool Is value null?
node.IsNil() bool Is node nil?
Modify
Function Description
node.MutateToString(v) Set value to string
node.MutateToInt(v) Set value to int
node.MutateToFloat(v) Set value to float64
node.MutateToBool(v) Set value to bool
node.MutateToNull() Set value to null
node.MutateToObject() Set value to empty object
node.MutateToArray() Set value to empty array
node.MutateToJSON(root, json) Set value to parsed JSON
node.MutateToField(name) Rename object field
node.MutateToNode(other) Copy another node's value
node.Suicide() Remove node from parent
node.AddField(name) *Node Add field to object, returns value node
node.AddElement() *Node Append element to array
node.InsertElement(pos) *Node Insert element at position
node.MergeWith(other) Merge other object's fields into this one
Encode
Function Description
node.Encode(buf) []byte Encode to byte slice, reusing buf
node.EncodeToByte() []byte Encode to new byte slice
node.EncodeToString() string Encode to string

Important Notes

Pool and Lifecycle

Decoded nodes live inside a pool managed by the Root. After calling Release(root), the Root and all its nodes are returned to the pool and must not be used. Accessing nodes after Release leads to undefined behavior.

root, _ := insaneJSON.DecodeString(`{"a":"b"}`)
node := root.Dig("a")

insaneJSON.Release(root)

// BUG: node belongs to the released root, this is undefined behavior
fmt.Println(node.AsString())

Always use defer insaneJSON.Release(root) right after decode.

Thread Safety

The top-level functions DecodeString, DecodeBytes, and Spawn are safe to call from multiple goroutines — they use sync.Pool internally.

However, a specific Root and its Nodes are not thread-safe. Do not share a Root between goroutines without synchronization. The typical pattern is one Root per goroutine:

// correct: each goroutine gets its own Root
for _, data := range items {
    go func(d []byte) {
        root, err := insaneJSON.DecodeBytes(d)
        if err != nil {
            return
        }
        defer insaneJSON.Release(root)
        // work with root...
    }(data)
}
Nil-safe Navigation

Dig on a nil node returns nil without panicking. This allows safe chaining:

// even if "a" doesn't exist, this won't panic — returns 0
value := root.Dig("a", "b", "c").AsInt()

As* methods on nil nodes return zero values ("", 0, false).

Use DigStrict when you need to distinguish "field not found" from "field is zero value":

node, err := root.DigStrict("user", "email")
if err != nil {
    // field doesn't exist
}
email, err := node.AsString()
if err != nil {
    // field exists but is not a string
}
Memory Management

For best performance, reuse Root objects instead of decoding into new ones:

root := insaneJSON.Spawn()
defer insaneJSON.Release(root)

for _, msg := range messages {
    root.DecodeBytes(msg)   // reuses internal buffers
    process(root)
}

Use root.ReleaseMem() after processing an unusually large JSON to free internal buffers:

root.DecodeBytes(hugeJSON)
process(root)
root.ReleaseMem() // release internal buffers to GC

Configuration

Variable Default Description
insaneJSON.StartNodePoolSize 128 Initial number of pre-allocated nodes per Root
insaneJSON.MapUseThreshold 16 Object field count above which Dig builds a hash map for O(1) lookup
insaneJSON.DisableBeautifulErrors false Set to true to skip formatting decode error messages for better performance
func init() {
    insaneJSON.StartNodePoolSize = 256
    insaneJSON.MapUseThreshold = 32
    insaneJSON.DisableBeautifulErrors = true
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	StartNodePoolSize      = 128
	MapUseThreshold        = 16
	DisableBeautifulErrors = false // set to "true" for best performance, if you have many decode errors

	// decode errors
	ErrEmptyJSON                    = errors.New("json is empty")
	ErrUnexpectedJSONEnding         = errors.New("unexpected ending of json")
	ErrUnexpectedEndOfString        = errors.New("unexpected end of string")
	ErrUnexpectedEndOfTrue          = errors.New("unexpected end of true")
	ErrUnexpectedEndOfFalse         = errors.New("unexpected end of false")
	ErrUnexpectedEndOfNull          = errors.New("unexpected end of null")
	ErrUnexpectedEndOfObjectField   = errors.New("unexpected end of object field")
	ErrExpectedObjectField          = errors.New("expected object field")
	ErrExpectedObjectFieldSeparator = errors.New("expected object field separator")
	ErrExpectedValue                = errors.New("expected value")
	ErrExpectedComma                = errors.New("expected comma")

	// api errors
	ErrRootIsNil = errors.New("root is nil")
	ErrNotFound  = errors.New("node isn't found")
	ErrNotObject = errors.New("node isn't an object")
	ErrNotArray  = errors.New("node isn't an array")
	ErrNotBool   = errors.New("node isn't a bool")
	ErrNotString = errors.New("node isn't a string")
	ErrNotNumber = errors.New("node isn't a number")
	ErrNotField  = errors.New("node isn't an object field")
)

Functions

func Fuzz

func Fuzz(data []byte) int

func Release

func Release(root *Root)

Types

type Node

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

Node Is a building block of the decoded JSON. There is seven basic nodes:

  1. Object
  2. Array
  3. String
  4. Number
  5. True
  6. False
  7. Null

And a special one – Field, which represents the field(key) on an objects. It allows to easily change field's name, checkout MutateToField() function.

func (*Node) AddElement

func (n *Node) AddElement() *Node

func (*Node) AddElementNoAlloc

func (n *Node) AddElementNoAlloc(root *Root) *Node

func (*Node) AddField

func (n *Node) AddField(name string) *Node

func (*Node) AddFieldNoAlloc

func (n *Node) AddFieldNoAlloc(root *Root, name string) *Node

func (*Node) AppendEscapedString

func (n *Node) AppendEscapedString(out []byte) []byte

func (*Node) AsArray

func (n *Node) AsArray() []*Node

func (*Node) AsBool

func (n *Node) AsBool() bool

func (*Node) AsBytes

func (n *Node) AsBytes() []byte

func (*Node) AsEscapedString

func (n *Node) AsEscapedString() string

func (*Node) AsFieldValue

func (n *Node) AsFieldValue() *Node

func (*Node) AsFields

func (n *Node) AsFields() []*Node

func (*Node) AsFloat

func (n *Node) AsFloat() float64

func (*Node) AsInt

func (n *Node) AsInt() int

func (*Node) AsInt64

func (n *Node) AsInt64() int64

func (*Node) AsString

func (n *Node) AsString() string

func (*Node) AsUint64

func (n *Node) AsUint64() uint64

func (*Node) Dig

func (n *Node) Dig(path ...string) *Node

Dig legendary insane dig function

func (*Node) DigField

func (n *Node) DigField(path ...string) *Node

func (*Node) DigStrict

func (n *Node) DigStrict(path ...string) (*StrictNode, error)

func (*Node) Encode

func (n *Node) Encode(out []byte) []byte

Encode legendary insane encode function uses already created byte buffer to place json data so mem allocations may occur only if buffer isn't long enough use it for performance

func (*Node) EncodeToByte

func (n *Node) EncodeToByte() []byte

EncodeToByte legendary insane encode function slow because it allocates new byte buffer on every call use Encode to reuse already created buffer and gain more performance

func (*Node) EncodeToString

func (n *Node) EncodeToString() string

EncodeToString legendary insane encode function slow because it allocates new string on every call use Encode to reuse already created buffer and gain more performance

func (*Node) InsertElement

func (n *Node) InsertElement(pos int) *Node

func (*Node) IsArray

func (n *Node) IsArray() bool

func (*Node) IsFalse

func (n *Node) IsFalse() bool

func (*Node) IsField

func (n *Node) IsField() bool

func (*Node) IsNil

func (n *Node) IsNil() bool

func (*Node) IsNull

func (n *Node) IsNull() bool

func (*Node) IsNumber

func (n *Node) IsNumber() bool

func (*Node) IsObject

func (n *Node) IsObject() bool

func (*Node) IsString

func (n *Node) IsString() bool

func (*Node) IsTrue

func (n *Node) IsTrue() bool

func (*Node) MergeWith

func (n *Node) MergeWith(node *Node) *Node

func (*Node) MutateToArray

func (n *Node) MutateToArray() *Node

func (*Node) MutateToBool

func (n *Node) MutateToBool(value bool) *Node

func (*Node) MutateToBytes

func (n *Node) MutateToBytes(value []byte) *Node

MutateToBytes mutate to a string and use byte slice as value. It doesn't copy data, so modifications of a slice will change result JSON.

func (*Node) MutateToBytesCopy

func (n *Node) MutateToBytesCopy(root *Root, value []byte) *Node

MutateToBytes mutate to a string and use byte slice as value. It copies data, so modification of a slice won't change result JSON.

func (*Node) MutateToEscapedString

func (n *Node) MutateToEscapedString(value string) *Node

func (*Node) MutateToField

func (n *Node) MutateToField(newFieldName string) *Node

MutateToField changes name of objects's field works only with Field nodes received by AsField()/AsFields() example: root, err := insaneJSON.DecodeString(`{"a":"a","b":"b"}`) root.AsField("a").MutateToField("new_name") root.Encode() will be {"new_name":"a","b":"b"}

func (*Node) MutateToFloat

func (n *Node) MutateToFloat(value float64) *Node

func (*Node) MutateToInt

func (n *Node) MutateToInt(value int) *Node

func (*Node) MutateToInt64

func (n *Node) MutateToInt64(value int64) *Node

func (*Node) MutateToJSON

func (n *Node) MutateToJSON(root *Root, json string) *Node

func (*Node) MutateToNode

func (n *Node) MutateToNode(node *Node) *Node

MutateToNode it isn't safe function, if you create node cycle, encode() may freeze

func (*Node) MutateToNull

func (n *Node) MutateToNull() *Node

func (*Node) MutateToObject

func (n *Node) MutateToObject() *Node

func (*Node) MutateToStrict

func (n *Node) MutateToStrict() *StrictNode

func (*Node) MutateToString

func (n *Node) MutateToString(value string) *Node

func (*Node) MutateToUint64

func (n *Node) MutateToUint64(value uint64) *Node

func (*Node) Suicide

func (n *Node) Suicide()

Suicide legendary insane suicide function

func (*Node) TypeStr

func (n *Node) TypeStr() string

type Root

type Root struct {
	*Node
	// contains filtered or unexported fields
}

Root is a top Node of decoded JSON. It holds decoder, current JSON data and pool of Nodes. Node pool is used to reduce memory allocations and GC time. Checkout ReleaseMem()/ReleasePoolMem()/ReleaseBufMem() to clear pools. Root can be reused to decode another JSON using DecodeBytes()/DecodeString(). Also Root can decode additional JSON using DecodeAdditionalBytes()/DecodeAdditionalString().

func DecodeBytes

func DecodeBytes(jsonBytes []byte) (*Root, error)

func DecodeFile

func DecodeFile(fileName string) (*Root, error)

func DecodeString

func DecodeString(json string) (*Root, error)

func Spawn

func Spawn() *Root

func (*Root) BuffCap

func (r *Root) BuffCap() int

BuffCap returns current size of internal buffer.

func (*Root) Clear

func (r *Root) Clear()

Clear makes Root empty object

func (*Root) DecodeBytes

func (r *Root) DecodeBytes(jsonBytes []byte) error

DecodeBytes clears Root and decodes new JSON. Useful for reusing Root to reduce allocations.

func (*Root) DecodeBytesAdditional

func (r *Root) DecodeBytesAdditional(jsonBytes []byte) (*Node, error)

DecodeBytesAdditional doesn't clean Root, uses Root node pool to decode JSON

func (*Root) DecodeFile

func (r *Root) DecodeFile(fileName string) error

DecodeFile clears Root and decodes new JSON. Useful for reusing Root to reduce allocations.

func (*Root) DecodeString

func (r *Root) DecodeString(json string) error

DecodeString clears Root and decodes new JSON. Useful for reusing Root to reduce allocations.

func (*Root) DecodeStringAdditional

func (r *Root) DecodeStringAdditional(json string) (*Node, error)

DecodeStringAdditional doesn't clean Root, uses Root node pool to decode JSON

func (*Root) PoolSize

func (r *Root) PoolSize() int

PoolSize returns how many Node objects is in the pool right now.

func (*Root) ReleaseBufMem

func (r *Root) ReleaseBufMem()

ReleaseBufMem sends internal buffer to GC. Useful to reduce memory usage after decoding big JSON.

func (*Root) ReleaseMem

func (r *Root) ReleaseMem()

ReleaseMem sends node pool and internal buffer to GC. Useful to reduce memory usage after decoding big JSON.

func (*Root) ReleasePoolMem

func (r *Root) ReleasePoolMem()

ReleasePoolMem sends node pool to GC. Useful to reduce memory usage after decoding big JSON.

type StrictNode

type StrictNode struct {
	*Node
}

StrictNode implements API with error handling. Transform any Node with MutateToStrict(), Mutate*()/As*() functions will return an error

func (*StrictNode) AsArray

func (n *StrictNode) AsArray() ([]*Node, error)

func (*StrictNode) AsBool

func (n *StrictNode) AsBool() (bool, error)

func (*StrictNode) AsBytes

func (n *StrictNode) AsBytes() ([]byte, error)

func (*StrictNode) AsEscapedString

func (n *StrictNode) AsEscapedString() (string, error)

func (*StrictNode) AsFieldValue

func (n *StrictNode) AsFieldValue() (*Node, error)

func (*StrictNode) AsFields

func (n *StrictNode) AsFields() ([]*Node, error)

func (*StrictNode) AsFloat

func (n *StrictNode) AsFloat() (float64, error)

func (*StrictNode) AsInt

func (n *StrictNode) AsInt() (int, error)

func (*StrictNode) AsInt64

func (n *StrictNode) AsInt64() (int64, error)

func (*StrictNode) AsString

func (n *StrictNode) AsString() (string, error)

func (*StrictNode) AsUint64

func (n *StrictNode) AsUint64() (uint64, error)

Jump to

Keyboard shortcuts

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