<aside> 💡

Go 1.25

Go 1.25 interactive tour

JSON evolution in Go: from v1 to v2

Green Tea GC: How Go Stopped Wasting 35% of Your CPU Cycles

</aside>

The second version of the json package coming in Go 1.25 is a big update, and it has a lot of breaking changes. The v2 package adds new features, fixes API issues and behavioral flaws, and boosts performance. Let's take a look at what's changed!

All examples are interactive, so you can read and practice at the same time.

The basic use case with Marshal and Unmarshal stays the same. This code works in both v1 and v2:

type Person struct {
    Name string
    Age  int
}
alice := Person{Name: "Alice", Age: 25}

// Marshal Alice.
b, err := json.Marshal(alice)
fmt.Println(string(b), err)

// Unmarshal Alice.
err = json.Unmarshal(b, &alice)
fmt.Println(alice, err)

But the rest is pretty different. Let's go over the main changes from v1.

Write/Read • Encode/Decode • Options • Tags • Custom marshaling • Default behavior • Performance • Final thoughts

MarshalWrite and UnmarshalRead

In v1, you used Encoder to marshal to the io.Writer, and Decoder to unmarshal from the io.Reader:

// Marshal Alice.
alice := Person{Name: "Alice", Age: 25}
out := new(strings.Builder) // io.Writer
enc := json.NewEncoder(out)
enc.Encode(alice)
fmt.Println(out.String())

// Unmarshal Bob.
in := strings.NewReader(`{"Name":"Bob","Age":30}`) // io.Reader
dec := json.NewDecoder(in)
var bob Person
dec.Decode(&bob)
fmt.Println(bob)

From now on, I'll leave out error handling to keep things simple.

In v2, you can use MarshalWrite and UnmarshalRead directly, without any intermediaries:

// Marshal Alice.
alice := Person{Name: "Alice", Age: 25}
out := new(strings.Builder)
json.MarshalWrite(out, alice)
fmt.Println(out.String())

// Unmarshal Bob.
in := strings.NewReader(`{"Name":"Bob","Age":30}`)
var bob Person
json.UnmarshalRead(in, &bob)
fmt.Println(bob)

They're not interchangeable, though:

MarshalEncode and UnmarshalDecode

The Encoder and Decoder types have been moved to the new jsontext package, and their interfaces have changed significantly (to support low-level streaming encode/decode operations).

You can use them with json functions to read and write JSON streams, similar to how Encode and Decode worked before: