Skip to content

Releases: buger/jsonparser

v1.6.1 — Fastest across all payload sizes (now benchmarks vs gjson + sonic)

Choose a tag to compare

@buger buger released this 30 Jul 14:06

🔒 Covered by ReqProof — L3 Assurance (123 requirements, 0 errors, 0 warnings)

Performance — gjson-style fast-skip

Ported gjson's >'\\' single-comparison fast-skip to three hot loops. The trick skips all non-structural bytes (those > 0x5C) in one unsigned comparison per byte, reducing branch overhead.

Payload Before After Change
Small (190B) 382 ns 339 ns -11.3%
Medium (2.4kB) 3,899 ns 3,141 ns -19.4%
Large (24kB) 20,788 ns 20,114 ns -3.2%

Now benchmarks against gjson and sonic

Added tidwall/gjson (15.5k⭐) and bytedance/sonic (9.6k⭐) to the benchmark suite.

Large payload — the definitive ranking:

Library time/op allocs
jsonparser 20,114 ns 0
gjson 22,756 ns 2
easyjson 33,771 ns 134
sonic 41,053 ns 71
ffjson 59,063 ns 144
encoding/json 130,565 ns 147

jsonparser is the fastest across ALL payload sizes and the only zero-allocation parser.

Full changelog: CHANGELOG.md

v1.6.0 — Append function + zero open known issues

Choose a tag to compare

@buger buger released this 29 Jul 08:36

🔒 Covered by ReqProof — L3 Assurance (123 requirements, 0 errors, 0 warnings, 0 open known issues)

New API: Append

// Append to an array without knowing its length
data, _ = jsonparser.Append(data, []byte(`"new_item"`), "items")

Append(data, value, keys...) ([]byte, error) — clean array-append API. Works on top-level and nested arrays. Auto-creates missing paths as single-element arrays. No need for [N] path syntax.

Bug fixes — all known issues resolved

KI Fix
KI-2 ParseInt("-") now returns MalformedValueError (was returning 0, nil)
KI-3 Disposition corrected to fixed (auto-coerce was implemented in v1.3.0)
KI-4 Set([1,2,3], val, "[5]") now appends instead of returning KeyPathNotFoundError

Zero open known issues. All 4 KIs are now status: fixed.

Full changelog: CHANGELOG.md

v1.5.1 — 6.1x large-payload speedup + fresh benchmarks

Choose a tag to compare

@buger buger released this 29 Jul 06:02

🔒 Covered by ReqProof — L3 Assurance (123 requirements, 0 errors, 0 warnings)

Performance — 6.1x large-payload speedup

Two optimizations found via ADHD-driven profiling + 10 parallel worktree experiments:

  1. Fix stringEnd unbounded backslash scanstringEndConfig was scanning the ENTIRE remaining parent document for backslashes instead of just the string body. Bounded to data[:firstQuote]. 128µs → 22µs (5.8x).

  2. SWAR string scan — replaced two separate bytes.IndexByte calls with a single inline 8-byte SWAR loop checking for both " and \ simultaneously. 22µs → 21µs (additional 8%).

Fresh benchmarks (all libraries at latest versions)

Methodology: Apple M4 Max (ARM64), Go 1.26.3, median of 5 runs. All comparison libraries updated. The encoding/json benchmark no longer uses ffjson-generated methods (fixed #126 in v1.3.1).

Payload jsonparser encoding/json easyjson jsonparser advantage
Small (190B) 382 ns / 0 allocs 1,335 ns / 9 allocs 312 ns / 4 allocs 3.5x faster, 0 allocs
Medium (2.4kB) 3,894 ns / 0 allocs 10,564 ns / 18 allocs 2,444 ns / 7 allocs 2.7x faster, 0 allocs
Large (24kB) 20,788 ns / 0 allocs 134,123 ns / 147 allocs 32,765 ns / 134 allocs 6.4x faster, 0 allocs

jsonparser is the fastest library overall on large payloads and the only zero-allocation parser.

Full changelog: CHANGELOG.md

v1.5.0 — Config (lenient parsing), streaming ReaderParser, name aliases

Choose a tag to compare

@buger buger released this 28 Jul 14:31

🔒 Covered by ReqProof — L3 Assurance (123 requirements, 0 errors, 0 warnings)

Config struct — opt-in lenient parsing (#160, #115)

var Lenient = jsonparser.Config{AllowSingleQuotes: true, AllowUnknownEscapes: true}
Lenient.Get(data, "key")  // parses {'key':'value'} and unknown escapes

Single-quote support and lenient escape handling via an opt-in Config. Default stays strict (RFC 8259).

Streaming ReaderParser (#132, #257)

rp := jsonparser.NewReaderParser(file)  // any io.Reader
rp.Get("users", "[0]", "name")          // path-based access from a stream

Path-based JSON access from an io.Reader — parse 10GB+ files without loading into memory. Buffers in 64KB chunks; memory bounded by the largest value.

Name aliases (#66)

EachArray, EachObject, EachArrayErr, EachArrayWildcard — canonical EachXxx pattern. Old XxxEach names kept for backward compatibility.

Proof coverage

  • 2 new SYS-REQs (115: Config/lenient, 116: streaming)
  • 123 requirements, 0 errors, 0 warnings, 279/279 functions traced

Full changelog: CHANGELOG.md

v1.4.0 — 9 new APIs, 20 issues resolved

Choose a tag to compare

@buger buger released this 28 Jul 10:29

New APIs (all backward-compatible)

Iteration with error/break control

  • ArrayEachErr — like ArrayEach but the callback returns error. Return io.EOF for graceful stop, any other error to abort. Resolves #53, #129, #176, #230, #255, #262.
  • EachKeyErr — same pattern for EachKey.

Safe string handling

  • Escape(s string) []byte — RFC 8259 string escaping (inverse of Unescape). Produces a quoted JSON string literal.
  • SetString(data, val, keys...)Set with auto-quoted value. No more invalid JSON from forgetting quotes. Resolves #144, #158, #218, #270.

Container accessors

  • GetArrayLen(data, keys...) (int, error) — count array elements without a callback. Resolves #175, #261.
  • GetObjectLen(data, keys...) (int, error) — count object key-value pairs.
  • GetUint64(data, keys...) (uint64, error) — uint64 variant of GetInt. Resolves #271.

Delete found signal

  • DeleteFound(data, keys...) ([]byte, bool) — returns whether the key was found. Resolves #229.

Wildcard paths

  • EachKeyWildcard, ArrayEachWildcard, SetWildcard[*] path component to iterate/set all elements. Resolves #112.
    jsonparser.SetWildcard(data, []byte("true"), "users", "[*]", "active")

JSONPath compiled paths

  • ParsePath("$.users[0].name")[]string{"users", "[0]", "name"}
  • CompilePath + CompiledPath — pre-compile and reuse with Get/Set/Delete/etc. Resolves #234, #251.
    path, _ := jsonparser.CompilePath("$.person.name.fullName")
    name, _ := path.Get(data)  // reuse across calls

Fixes

  • EachKey no longer panics with >64 key components (#56)
  • Set pre-allocates output buffer, reducing allocations from 6 to 1 (#107)

Proof coverage

  • 3 new SYS-REQs (112: container length, 113: wildcard paths, 114: compiled paths)
  • 121 total requirements, 0 errors, 0 warnings, 384 MC/DC witness rows (0 uncovered)
  • All new APIs traced via source-native annotations

Acknowledgments

Implemented by codex (gpt-5-codex) via codex exec. Proof coverage by ReqProof.

Full changelog: CHANGELOG.md

v1.3.1 — Bug fixes + proof strengthening

Choose a tag to compare

@buger buger released this 28 Jul 06:41

Bug fixes

  • Fix Set/Delete input-buffer aliasing (#209, #141) — Set and Delete no longer corrupt the caller's input []byte when the slice has spare capacity. The append() call path was writing into the backing array beyond the returned slice. Now all mutation paths allocate a fresh buffer.

  • Fix EachKey array-index inconsistency (#232) — EachKey now descends into terminal array-index paths (e.g. "key", "[0]") consistently with Get. Previously EachKey returned empty where Get succeeded on the same path.

  • Fix benchmark measuring ffjson, not encoding/json (#126) — the benchmark payload types had ffjson-generated MarshalJSON/UnmarshalJSON methods, so the "10x faster than encoding/json" comparison was silently measuring ffjson. Now uses plain types with no generated methods.

Proof strengthening (how these bugs escaped, and the gates that prevent recurrence)

Bug Proof gap New gate
#209/#141 Set aliasing No obligation said "Set must not mutate the input buffer" New obligation no_input_mutation + assertInputUnchanged gate (snapshots input + backing-array capacity before every Set/Delete, verifies unchanged after)
#232 EachKey ≠ Get No obligation said "EachKey must resolve paths identically to Get" New obligation api_consistency + TestApiConsistencyEachKeyMatchesGet gate (random JSON + paths, asserts EachKey result == Get result)
#126 benchmark ffjson Proof didn't cover the benchmark suite Benchmark honesty lint: verifies no benchmark type implements json.Marshaler/json.Unmarshaler

Contributed by codex (gpt-5-codex) via codex exec.

v1.3.0 — Formally verified by ReqProof (L3 Assurance)

Choose a tag to compare

@buger buger released this 27 Jul 20:16

🔒 Formally verified by ReqProof

jsonparser v1.3.0 is the first Go library proven to L3 assurance by ReqProof — a git-native requirements-engineering and formal-verification platform. Every public API is traced to a formal requirement, every requirement is tested with 100% MC/DC coverage, and the entire parser is fuzzed by a custom structure-aware JSON fuzzer at 250k inputs/sec.

The proof review caught 7 real bugs that years of community use, OSS-Fuzz, and standard fuzzing had missed.

Read the root-cause analysis →


Security / bug fixes

  • Fix Delete panic on malformed input with leading comma (OSS-Fuzz 4649128545288192)
  • Fix empty-string key-component panics (8 sites) — reported by @c-tonneslan (#284)
  • Fix Set data loss on scalar arrays (#267) — reported by @Solaris-star (#286)
  • Fix Set malformed-JSON output on cross-type paths (auto-coerce)
  • Fix Delete trailing-comma malformation
  • Fix ArrayEach spurious callback on non-array root
  • Fix lone-Unicode-surrogate mishandling in Unescape (now matches encoding/json)

Performance

  • parseInt fast-path — 22–37% faster on short integers — @trevorprater (#285)
  • stringEnd SIMD fast path — 12× faster on no-escape strings

Full changelog: CHANGELOG.md

Acknowledgments

Thank you to @c-tonneslan, @Solaris-star, @trevorprater, and OSS-Fuzz for the reports and contributions.

v1.2.0

Choose a tag to compare

@buger buger released this 29 Apr 05:50
c172c16

What's Changed

Full Changelog: v1.1.2...v1.2.0

v1.1.2

Choose a tag to compare

@buger buger released this 19 Mar 12:48
a69e7e0

What's Changed

New Contributors

Full Changelog: v1.1.1...v1.1.2

v1.1.1

Choose a tag to compare

@buger buger released this 08 Jan 09:43
df3ea76