buger/jsonparser: path-based JSON access in Go without structs
THE fastest alternative JSON parser for Go that does not require schema
At a glance
- What is it?
- buger/jsonparser reads fields from JSON by key path instead of unmarshalling into structs, and its README claims up to 6.5x the speed of encoding/json with no allocations. Here is how it installs, what the path API actually does, and where it stops being the right tool.
- Who is it for?
- Adopt buger/jsonparser when you are pulling a handful of fields out of third-party or loosely specified JSON and do not want to maintain struct definitions for it. Do not adopt it when you need to decode a whole document into typed Go values, or when your input is JSON that violates RFC 8259 and you cannot enumerate which extension it uses, since the package-level functions stay strict.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 41 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem jsonparser solves: fields without structs
The author's stated motivation is third-party APIs that are unpredictable and complex. With encoding/json you either define a struct that matches the payload exactly, or you decode into map[string]interface{} and pay for it in speed and in code that is hard to read. buger/jsonparser takes a third route: you hand it the raw bytes plus the path to the field you want, and it returns that field.
The package README describes it as not requiring you to know the structure of the payload, allowing access to fields by providing the path to them. That is the whole pitch. A response with forty keys where you care about three does not need a forty-field struct, and it does not need a generic map either. This suits services that aggregate many upstream APIs, and it suits code that has to tolerate an upstream adding or renaming keys without a compile error.
The claim attached to that design is performance: the README states it is up to 6.5x faster than the standard encoding/json package depending on payload size and usage, and that it allocates no memory. Treat those as the project's own figures, not as a general guarantee. The qualifier about payload size and usage is doing real work in that sentence.
How path lookup works: byte slices, not decoded values
The mechanism is visible in the example the README gives. You call jsonparser.Get(data, "person", "name", "fullName"), passing the raw []byte and the key segments as separate string arguments. There is no intermediate decoded tree. When you ask for an object, the documentation says the function returns a []byte slice pointing into the original data, so a call like jsonparser.Get(data, "company") yields the bytes {"name": "Acme"} rather than a Go map.
That pointer-into-the-buffer behavior is the source of both the speed and the sharp edge. Because the result aliases the input, it stays valid only as long as the underlying buffer does. The README makes this explicit for streaming: with ArrayEach on a ReaderParser, each callback value is valid for the duration of the callback, and you must copy it if you need to keep it. The same reasoning applies to any slice you retain past the life of the source bytes.
Typed helpers sit on top of the same path walk. GetInt and GetBoolean exist for when you already know the field type, and GetString is used in the README's own index example, jsonparser.GetString(data, "person", "avatars", "[0]", "url"). Array elements are addressed with a bracketed index as a path segment, which keeps arrays on the same lookup model as objects. Iteration is handled by EachArray, EachObject and EachKey; the last takes a slice of paths and a callback, and the README presents it as the most efficient way to extract multiple keys, since one pass over the document serves all of them.
Errors follow the same shape. A missing key returns an error rather than a zero value, and the README's example shows the idiomatic check: if value, err := jsonparser.GetInt(data, "company", "size"); err == nil. For a library whose selling point is working without a schema, returning an error on an absent path is the honest choice, but it does mean every extraction site carries error handling.
Installing jsonparser and reading your first field
The module path is github.com/buger/jsonparser and go.mod declares go 1.13, so any modern Go toolchain can build it. The README's example imports the package and calls Get with the key path. Given a document with a person object, this prints the full name:
import "github.com/buger/jsonparser"
name, err := jsonparser.GetString(data, "person", "name", "fullName")
if err != nil {
log.Fatal(err)
}
fmt.Println(name)You should see the string stored at that path. If the path does not exist, err is non-nil and name is empty, so the error check is not optional.
For numeric fields, GetInt returns an int64 and avoids converting a string yourself:
jsonparser.GetInt(data, "person", "github", "followers")If your JSON comes from a file or a network stream rather than a byte slice, the README documents ReaderParser, which keeps the same path-based lookup model over an io.Reader:
file, err := os.Open("large.json")
if err != nil {
log.Fatal(err)
}
defer file.Close()
parser := jsonparser.NewReaderParser(file)
name, err := parser.GetString("person", "name")Note that the ReaderParser methods take the path segments directly, without the leading data argument. To walk a root array incrementally, ArrayEach on the parser invokes a callback per element, and the README repeats the aliasing caveat there: copy the value if you must retain it.
Lenient mode and the strict-by-default boundary
The package-level functions are strict RFC 8259 parsers, and the README says so plainly. Real-world JSON is not always strict. Single-quoted strings and non-standard escape sequences show up in config files and in output from hand-rolled serializers. Rather than loosening the default, the library exposes a Config type with two extension flags: AllowSingleQuotes and AllowUnknownEscapes.
You can opt into both at once through the package-level Lenient value:
data := []byte(`{'name':'Ada','role':'engineer'}`)
name, err := jsonparser.Lenient.GetString(data, "name")
// name == "Ada"Or enable only the extension your input actually needs, which the README demonstrates with a Config literal:
config := jsonparser.Config{AllowUnknownEscapes: true}
data := []byte("{\"path\":\"docs\\`draft\\x\"}")
path, err := config.GetString(data, "path")
// path == "docs`draftx"The README states that the config-aware Get, GetString, Set, Delete, ArrayEach and ObjectEach methods share the same signatures and behavior as their package-level counterparts apart from the enabled extensions, and that jsonparser.DefaultConfig is strict. This is a better arrangement than a global flag, because the leniency is scoped to a value you construct and pass. The cost is that you now thread a config through call sites that previously needed nothing, and a codebase that mixes both styles has two ways to call the same operation.
Where jsonparser is the wrong choice
The clearest failure mode is aliasing. Because Get returns a slice pointing into the input buffer, retaining that slice after the buffer is reused or freed gives you stale or corrupted data. The README warns about this for the streaming callbacks but the hazard is not confined to them. If you read chunks into a reused buffer and keep field values across iterations, you will get wrong results that no compiler will catch.
The second boundary is scope. This is a field extractor, not a decoder. If you need the whole document as typed Go values, with nested structs, custom unmarshalling and the rest of the encoding/json contract, jsonparser does not offer that, and building it on top of Get calls means writing the schema you were trying to avoid. The README's own rationale is about avoiding struct definitions, so a project that wants structs is not the target audience.
The third is leniency granularity. If your input violates the spec in a way that neither AllowSingleQuotes nor AllowUnknownEscapes covers, the strict package-level functions reject it and there is no documented general-purpose escape hatch. You are left pre-processing the bytes yourself.
Finally, the performance claim is conditional by the README's own wording, tied to payload size and usage. A workload that reads every field of a small document, or that would benefit from a single decoding pass into a struct, is not obviously the case where a path-based parser wins. The project ships a benchmark directory and a Makefile bench target for exactly this reason.
Alternatives and how their approach differs
The README names ffjson and easyjson as the few libraries it found with their own parsers rather than wrappers around encoding/json. The distinction it draws is that both still require you to create data structures. easyjson generates marshalling code from your struct definitions; ffjson does the same kind of code generation. That buys speed while keeping typed access, but it means a build step and a schema you maintain, which is the opposite trade from jsonparser's path lookup.
The Dockerfile in the repository also pulls in a set of comparison libraries for benchmarking: Jeffail/gabs, bitly/go-simplejson, pquerna/ffjson, antonholmquist/jason, mreiferson/go-ujson, ugorji/go/codec and mailru/easyjson. gabs and jason are the closest in spirit, offering dynamic access without structs; the difference is that jsonparser works on raw bytes with a path argument list and returns slices into the buffer, while those libraries build their own object representations you then query. That representation is what jsonparser avoids allocating.
If your JSON is strict and your access pattern is whole-document, encoding/json remains the baseline against which this project measures itself, and it comes with the standard library's stability and tooling. Choosing jsonparser is choosing a narrower, faster path lookup over a general decoder.
Maintenance, licence and what the proof work implies
The repository is not archived, and the last push was on 2026-08-21, so it is being touched. Recent releases are close together: v1.5.1 on 2026-07-29, v1.6.0 on 2026-07-29 and v1.6.1 on 2026-07-30. The v1.6.0 notes mention an Append function and zero open known issues; v1.5.1 mentions a 6.1x large-payload speedup and fresh benchmarks.
The licence is MIT, which permits commercial and closed-source use with the usual requirement to carry the copyright notice and permission text. That is a permissive licence with no copyleft obligation, but read the LICENSE file rather than this summary; nothing here is legal advice.
Upgrade cost deserves attention because of the verification story. The README describes jsonparser as the reference case study for ReqProof, a requirements-engineering and formal-verification platform, with 118 traced requirements, 100% MC/DC coverage claims, and 16M+ fuzz executions. The release notes for v1.6.0 and v1.6.1 read as a project that treats each change as something to re-verify, and the repository carries proof.yaml, a proof/ directory and a specs/ directory alongside the Go sources. That is a heavier process than most Go libraries run, and it is the main reason to expect changes to be deliberate rather than rapid. The practical cost is on your side: if you depend on exact error strings or on the precise behavior of Set and Delete, check the changelog before bumping, since the proof review is documented as having fixed a data-loss bug in Set and a malformed-output bug in Delete.
Editorial conclusion
Adopt buger/jsonparser when you are pulling a handful of fields out of third-party or loosely specified JSON and do not want to maintain struct definitions for it. Do not adopt it when you need to decode a whole document into typed Go values, or when your input is JSON that violates RFC 8259 and you cannot enumerate which extension it uses, since the package-level functions stay strict. Before committing, run the package's own test suite against a sample of your production payloads, because the README's performance figures are tied to payload size and usage patterns rather than to your data.
Frequently asked questions
What is buger/jsonparser and who is it for?
It is a Go JSON library that does not require you to know the structure of the payload, letting you access fields by providing the path to them. It targets code that consumes unpredictable third-party APIs and wants to avoid defining structs.
How do I use buger/jsonparser to read a field?
Import github.com/buger/jsonparser and pass the raw bytes plus the key path segments to Get, GetString or GetInt, for example jsonparser.GetString(data, "person", "name", "fullName"). A missing key returns an error rather than a zero value.
Does buger/jsonparser parse single-quoted JSON?
The package-level functions remain strict RFC 8259 parsers. For inputs that use single-quoted strings or non-standard escapes you must use a Config explicitly, or the Lenient value, which enables both AllowSingleQuotes and AllowUnknownEscapes.
Can buger/jsonparser stream a large JSON document instead of loading it all?
Yes. ReaderParser provides the same path-based lookup model for JSON read from an io.Reader, so a large document does not need to be loaded into a single byte slice. Each callback value is valid only for the duration of the callback, so copy it if it must be retained.
What is the licence of buger/jsonparser?
The repository is licensed under MIT. That permits commercial and closed-source use provided the copyright notice and permission text are carried, but read the LICENSE file for the actual terms.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/buger-jsonparser)