Introduction
Originally published in Japanese at https://zenn.dev/ymotongpoo/books/go-json-v2-history/viewer/00-introduction.
Write the following code in Go, and the Name field ends up holding gopher.
type User struct {
Name string `json:"name"`
}
var u User
json.Unmarshal([]byte(`{"NAME":"gopher"}`), &u)
The JSON key is NAME and the name specified in the struct tag is name, yet they still match. nAmE or Name would match just the same. It is surprising the first time you run into it, and it felt wrong to me too. But this is not a bug. It is specified behavior: the encoding/json documentation explicitly states that matching is case-insensitive.
And precisely because it was specified, the behavior went unchanged for 14 years.
In 2016, an issue was filed asking whether this was really right. Security concerns were raised, and someone even wrote an actual patch to fix it. It still wasn’t fixed. Until encoding/json/v2 officially landed in Go 1.27, this behavior never changed once.
Why wasn’t it fixed? Why did it end up being rebuilt under a different name instead of being fixed? And after 14 years, what shape did it finally land in?
Part of the answer lies outside the standard library. The fast JSON libraries such as goccy/go-json and bytedance/sonic exist precisely because v1 could not be fixed. And in the pursuit of speed, they took on the odd job of faithfully replicating even v1’s flaws. That faithfulness produced real bugs.
Prerequisites
You need only three pieces of background.
encoding/jsonis a standard package that has existed since Go 1.0 (2012);Marshalconverts Go values to JSON, andUnmarshalconverts JSON to Go values.- Go has a backward compatibility guarantee. Programs written to the Go 1 specification will continue to compile and behave the same on later versions of Go.
GOEXPERIMENTis an environment variable specified at build time, used to enable features that are not yet official or to revert a new default to its old behavior. It is distinct fromGODEBUG, which takes effect at run time.