Go
The Road to JSON V2: Exploring Go's Experimental New API
Published:
•
Duration: 6:52
0:00
0:00
Transcript
Host: Alex Chan
Guest: Marcus Thorne
Host: Hey everyone, welcome back to Allur. I’m your host, Alex Chan. Today, we’re diving deep into the world of Go—or Golang, if you prefer. Now, if you’ve been building microservices or cloud-native tools in Go over the last decade, you’ve definitely spent a lot of time with the `encoding/json` package. It is the workhorse of the ecosystem. But, let’s be real for a second—it’s also been a bit of a "love-hate" relationship. It’s reliable, sure, but it’s heavy on reflection, it can be a memory hog, and some of the design choices from back in 2011 don’t always fit the modern web.
Guest: Thanks for having me, Alex! It’s a great time to be talking about Go. The JSON V2 stuff is probably the most exciting thing I’ve seen in the standard library in years.
Host: It really does feel like a "finally!" moment, doesn't it? I mean, `encoding/json` has been there since the beginning. Why now? Why did it take until 1.25 to see this experimental V2?
Guest: Honestly, it’s the "Go way," right? The team is incredibly cautious about breaking things. The current V1 package is essentially set in stone because of the Go 1 compatibility promise. But as Go became the backbone of the cloud, developers started hitting walls. I’ve worked on systems where 30 or 40 percent of our CPU time was just… spent on JSON marshaling. And because V1 relies so heavily on reflection—basically looking at your structs at runtime to figure out what to do—it creates these massive memory allocations. You end up with these "garbage collection spikes" that are just a headache to manage.
Host: Oh, I’ve definitely been there. You look at a flame graph and it’s just all `reflect`. So, V2 is a "clean-slate" approach. But they didn't just make one package—they made two. We have `encoding/json/jsontext` and `encoding/json/v2`. Why the split?
Guest: This is actually the smartest part of the design. In the original V1, the low-level logic—like actually parsing the bytes—was all tangled up with the high-level logic, which is mapping those bytes to Go structs. In V2, they’ve separated those concerns. `jsontext` is the "engine room." It’s a low-level, token-based API. It doesn't care about your structs; it just sees a stream of JSON tokens.
Host: So `jsontext` is for when you want to get your hands dirty with the raw data?
Guest: Exactly. If you’re processing a massive 2-gigabyte JSON file, you don’t want to unmarshal the whole thing into memory. With `jsontext`, you can just stream it. It has native support for things like JSON Lines—you know, NDJSON—which is a godsend for log processing. You can iterate through a huge array, process one item at a time, and your memory usage stays almost flat. It’s actually… um, it’s surprisingly satisfying to use compared to the old way.
Host: Interesting! So then the `v2` package is where most of us will spend our time? The `Marshal` and `Unmarshal` we know and love?
Guest: Right. The `v2` package is the high-level stuff. It’s been redesigned to be "buffer-aware." Basically, it’s much better at reusing memory. But for me, the biggest "aha moment" was the error handling. Have you ever tried to debug a JSON error in a deeply nested struct in V1?
Host: (Laughs) Oh, it’s a nightmare! It just tells you something like "invalid character at byte 4000" and you’re left hunting through this massive object.
Guest: Exactly! V2 is much more communicative. It identifies exactly where the type mismatch happened in your struct hierarchy. It’s a small thing, but when you’re debugging an API integration at 2 AM, it’s life-saving.
Host: That sounds like a dream. Now, let’s talk about struct tags. I saw that `omitzero` is finally a thing. Can you explain why that’s such a big deal compared to the old `omitempty`?
Guest: Oh, man. `omitempty` is probably the most misunderstood feature in Go. In V1, it only works if a field is "empty" by a very specific definition. But if you have a struct that’s a zero value—like a nested struct—`omitempty` often wouldn't skip it. Or if you had a custom type, it was just… finicky. `omitzero` in V2 uses the `IsZero()` logic that was introduced back in Go 1.13. It’s much more intuitive. If the value is the "zero" for that type, it’s gone. No more weird empty objects hanging out in your output just because the tag didn't behave.
Host: That is a huge quality-of-life improvement. And I also noticed case-insensitive matching? Tell me we can finally stop writing `json:"myField"` and `json:"my_field"` tags for every single API.
Guest: (Laughs) Yes! You can literally pass an option like `json.WithMatchCaseInsensitiveNames()`. It’s actually really common when you’re consuming external APIs that are—let's say—"inconsistent" with their casing. Now you can just handle it at the decoder level rather than littering your structs with multiple tags or writing custom logic.
Host: I love that. It feels like they really listened to the community feedback from the last decade. But since this is experimental, how do we actually use it? I mean, I’m assuming we shouldn't just go and delete all our V1 code tomorrow?
Guest: Definitely not. The Go team is very clear that this is a "dual-track" approach. V1 isn't going anywhere. It’ll be in the standard library basically forever for backward compatibility. V2 is currently in the experimental phase, so you have to import it as `encoding/json/v2`. It’s technically in the `x/` repository paths or experimental paths depending on how you're tracking the release. The idea is to let us, the developers, stress-test it. They want to know where it breaks or where the API feels clunky before they finalize it.
Host: So if I have a high-performance service, maybe I just port one or two heavy endpoints first?
Guest: That’s exactly what I’d recommend. Find that one endpoint that’s showing up as a bottleneck in your profiles. Swap it to V2, see if the memory allocations drop—spoiler: they probably will—and give feedback. There are still some "pluggable marshaling" features they're tweaking, where you can actually write custom logic that’s aware of the "state" of the encoding, like how deep you are in a nested structure. It’s very powerful, but it’s the kind of thing that needs real-world testing.
Host: It’s really interesting to see Go evolving like this. It feels like they’re trying to stay competitive with these third-party libraries like `json-iterator` or `easyjson` without losing the simplicity of the standard library.
Guest: Exactly. For a long time, if you wanted performance, you *had* to go third-party. And that comes with risks—dependency bloat, maintenance issues. If the standard library can get within 90% of the performance of those highly optimized libraries while being safer and easier to use? That’s a win for everyone.
Host: Absolutely. Well, Marcus, this has been such a great breakdown. I’m definitely going to be playing around with `jsontext` this weekend for some log parsing I’ve been procrastinating on.
Guest: (Laughs) It’s more fun than it sounds, I promise!
Host: I’ll take your word for it! Before we wrap up, where should people go if they want to dive into the specs or see some code examples?
Guest: The Go Blog is the best place to start. Joe Tsai wrote a fantastic, in-depth post about the vision for V2. And of course, just check out the Go 1.25 release notes. The documentation for the new `jsontext` package is already pretty solid.
Host: Perfect. Marcus, thank you so much for joining us on Allur and sharing your expertise.
Guest: My pleasure, Alex. Thanks for having me!
Host: And thanks to all of you for tuning in. Go 1.25 and the JSON V2 experiment represent a huge shift in how we’ll be writing Go in the future. If you’re a backend dev, now is the time to start auditing those hot paths and see how these new tools can optimize your workflow. As always, you can find the links and resources in the show notes at allur.tech. I’m Alex Chan, and you’ve been listening to Allur. We’ll see you in the next episode!
Tags
Go
Golang
backend
performance
modernization
cloud-native
json