Go library to provide lazy, or just-in-time (jit) parsing of JSON encodings and Go values.
Traditional parsing with json.Marshal or json.Unmarshal processes all data immediately, even if encoding or values are never used. Unnecessary parsing leads to wasted CPU cycles in processing, unnecessary memory allocations, and increased pressure on garbage collection operations. Use jitjson as placeholders to store json encodings or Go values until json parsing is actually needed.
- 🪶 Zero dependencies
- 🛠️ Support for encoding/json/v2
- 🔗 Integrates with encoding/json interfaces
- 💾 Reduce memory when working with large JSON datasets
- 🚀 Improves performance by avoiding unnecessary parsing of JSON
If you intend to use all of your data, jitjson will not provide any benefit.
This library requires Go version >=1.18:
$ go get github.com/mcwalrus/go-jitjsonFor Go 1.25+, jitjson provides support for encoding/json/v2.
$ export GOEXPERIMENT=jsonv2
$ go doc github.com/mcwalrus/go-jitjson.JitJSONV2For more information on json/v2, see the offical blog post.
package main
import (
"fmt"
"encoding/json"
"github.com/mcwalrus/go-jitjson"
)
type Person struct {
Name string
Age int
City string
}
func main() {
// jitjson: stores value
jit := jitjson.New(Person{
Name: "John",
Age: 30,
City: "New York",
})
// json.Marshal: parses json encoding
data, err := json.Marshal(jit)
if err != nil {
panic(err)
}
fmt.Println(string(data)) // Output: {"age":30,"city":"New York","name":"John"}
}Use the NewFromBytes method to create a JitJSON from a JSON encoded string.
package main
import (
"fmt"
"encoding/json"
"github.com/mcwalrus/go-jitjson"
)
type Person struct {
Name string
Age int
City string
}
func main() {
// json encoding
data := `{
"Name": "John",
"Age": 30,
"City": "New York"
}`
// json.Unmarshal: stores json encoding
var jit = jitjson.NewFromBytes[Person]{}
err := json.Unmarshal(data, jit)
if err != nil {
panic(err)
}
// jitjson: parses value only when needed
value, err := jit.Unmarshal()
if err != nil {
panic(err)
}
fmt.Println(value) // Output: {John 30 New York}
}Consider when your target value is a slice:
package main
import (
"fmt"
"github.com/mcwalrus/go-jitjson"
)
type Person struct {
Name string
Age int
City string
}
func main() {
jsonArray := []byte(`[
{"Name":"John","Age":30,"City":"New York"},
{"Name":"Jane","Age":25,"City":"Los Angeles"},
{"Name":"Jim","Age":35,"City":"Chicago"}
]`)
// Unmarshal slice
var jit []*jitjson.JitJSON[Person]
err := json.Unmarshal(jsonArray, &jit)
if err != nil {
panic(err)
}
// Unmarshal only the first person ...
value, err := jit[0].Unmarshal()
if err != nil {
panic(err)
}
fmt.Println(value) // Output: {John 30 New York}
}Consider when your target value is a map:
package main
import (
"fmt"
"github.com/mcwalrus/go-jitjson"
)
type Person struct {
Name string
Age int
City string
}
func main() {
jsonMap := []byte(`{
1: {"Name":"John","Age":30,"City":"New York"},
2: {"Name":"Jane","Age":25,"City":"Los Angeles"},
3: {"Name":"Jim","Age":35,"City":"Chicago"}
}`)
// Unmarshal map
var jitMap map[int]*jitjson.JitJSON[Person]
err := json.Unmarshal(jsonMap, &jitMap)
if err != nil {
panic(err)
}
// Select a person
jit, ok := jitMap[1]
if !ok {
panic("missing person")
}
// Unmarshal only person (1) ...
person, err := jit.Unmarshal()
if err != nil {
panic(err)
}
fmt.Println(person) // Output: {John 30 New York}
}Consider when you have nested fields you would want to avoid parsing:
package main
import (
"fmt"
"github.com/mcwalrus/go-jitjson"
)
type Address struct {
Street string
City string
Zip string
}
type Person struct {
Name string
Age int
Address *jitjson.JitJSON[Address]
}
func main() {
jsonData := []byte(`{
"Name": "John",
"Age": 30,
"Address": {
"Street": "123 Main St",
"City": "New York",
"Zip": "10001"
}
}`)
// Unmarshal person
var person Person
err := json.Unmarshal(jsonData, &person)
if err != nil {
panic(err)
}
// Unmarshal person's address just-in-time ...
address, err := person.Address.Unmarshal()
if err != nil {
panic(err)
}
fmt.Println(address) // Output: {123 Main St New York 10001}
}Use the Set \ SetBytes methods to update values of JitJSON. Using these methods will force re-parsing of results:
package main
import (
"fmt"
"github.com/mcwalrus/go-jitjson"
)
func main() {
// New JitJSON:
jit := jitjson.New(Person{
Name: "John",
Age: 30,
City: "New York",
})
// Initial encoding ...
jsonEncoding, err := jit.Marshal()
if err != nil {
panic(err)
}
// Update the value:
jit.Set(Person{
Name: "Jane",
Age: 25,
City: "Los Angeles",
})
// Updated encoding ...
jsonEncoding, err = jit.Marshal()
if err != nil {
panic(err)
}
fmt.Println(string(jsonEncoding)) // Output: {"age":25,"city":"Los Angeles","name":"Jane"}
}Values can be parsed multiple times, which doesn't come with any performance penalty.
package main
import (
"fmt"
"github.com/mcwalrus/go-jitjson"
)
type Person struct {
Name string
Age int
City string
}
func main() {
jit := jitjson.New(Person{
Name: "John",
Age: 30,
City: "New York",
})
// Initial Marshal ...
_, err := jit.Marshal()
if err != nil {
panic(err)
}
// No new allocations
for i := 0; i < 10; i++ {
_, err = jit.Marshal()
if err != nil {
panic(err)
}
}
◊
jit = jitjson.NewFromBytes([]byte(`{
"name": "John",
"age": 30,
"city": "New York"
}`))
// Initial Unmarshal ...
_, err = jit.Unmarshal()
if err != nil {
panic(err)
}
// No new allocations
for i := 0; i < 10; i++ {
_, err = jit.Unmarshal()
if err != nil {
panic(err)
}
}
}See BENCHMARK.md for comprehensive benchmark results and performance analysis.
Please report any issues or feature requests to the GitHub repository.
This module is maintained by Max Collier under an MIT License Agreement.