mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 22:53:34 +08:00
As an experiment, with a thought for how Gin might eventually migrate to
and use features from jsonv2, I have put together a new json/codec for
json/v2.
---
Selected with -tags jsonv2, and constrained to go1.27 &&
goexperiment.jsonv2 since encoding/json/v2 is still gated by the
experiment in 1.27 and GOEXPERIMENT=nojsonv2 makes it unimportable.
json.go negates that entire expression, mirroring how it already
negates sonic's OS constraint, so -tags jsonv2 on Go 1.25/1.26 falls
back to encoding/json rather than leaving API nil.
Uses json.DefaultOptionsV1() to keep behaviour identical to the v1
codec: v2's native defaults would render nil slices/maps as []/{} and
match member names case-sensitively, silently zeroing fields that
previously bound.
Fills three v1 gaps v2's API doesn't cover directly: UseNumber has no
exported option, so it's reproduced with a custom *any unmarshaler;
MarshalWrite omits Encoder.Encode's trailing newline; MarshalIndent
becomes jsontext.WithIndentPrefix + WithIndent.
118 lines
2.9 KiB
Go
118 lines
2.9 KiB
Go
// Copyright 2026 Gin Core Team. All rights reserved.
|
|
// Use of this source code is governed by a MIT style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package json_test
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin/codec/json"
|
|
)
|
|
|
|
// These benchmarks compare codecs, and on go1.27 also compare the engine behind
|
|
// encoding/json: the jsonv2 GOEXPERIMENT is on by default there, so v1 is itself
|
|
// implemented over encoding/json/v2. To see a codec or engine difference, run
|
|
// the same benchmark under each configuration and compare with benchstat, e.g.
|
|
//
|
|
// go test -bench . -count 10 ./codec/json/ > new.txt
|
|
// GOEXPERIMENT=nojsonv2 go test -bench . -count 10 ./codec/json/ > old.txt
|
|
// benchstat old.txt new.txt
|
|
//
|
|
// Marshal and Unmarshal do not necessarily move in the same direction, so read
|
|
// them separately rather than as one number.
|
|
|
|
type benchPayload struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Tags []string `json:"tags"`
|
|
Meta map[string]string `json:"meta"`
|
|
Nested []benchPayload `json:"nested,omitempty"`
|
|
}
|
|
|
|
var nested = benchPayload{
|
|
ID: 1,
|
|
Name: "gin <framework>",
|
|
Tags: []string{"a", "b", "c"},
|
|
Meta: map[string]string{"k1": "v1", "k2": "v2"},
|
|
Nested: []benchPayload{
|
|
{ID: 2, Name: "x", Tags: []string{"z"}},
|
|
{ID: 3, Name: "y", Meta: map[string]string{"q": "r"}},
|
|
},
|
|
}
|
|
|
|
// flat carries no map, so it isolates struct and slice encoding from the key
|
|
// sorting that marshaling a map requires.
|
|
type benchFlat struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Tags []string `json:"tags"`
|
|
N float64 `json:"n"`
|
|
OK bool `json:"ok"`
|
|
}
|
|
|
|
var flat = []benchFlat{
|
|
{1, "alpha <a>", []string{"x", "y"}, 1.5, true},
|
|
{2, "beta", []string{"z"}, 2.25, false},
|
|
{3, "gamma", nil, 3.75, true},
|
|
}
|
|
|
|
// Safe at package scope: this is an external test package, so codec/json is
|
|
// fully initialised and API is installed before these run.
|
|
var nestedJSON, _ = json.API.Marshal(nested)
|
|
|
|
func BenchmarkMarshal(b *testing.B) {
|
|
for b.Loop() {
|
|
if _, err := json.API.Marshal(nested); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkMarshalFlat(b *testing.B) {
|
|
for b.Loop() {
|
|
if _, err := json.API.Marshal(flat); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkMarshalIndent(b *testing.B) {
|
|
for b.Loop() {
|
|
if _, err := json.API.MarshalIndent(nested, "", " "); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkUnmarshal(b *testing.B) {
|
|
for b.Loop() {
|
|
var out benchPayload
|
|
if err := json.API.Unmarshal(nestedJSON, &out); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkEncoder(b *testing.B) {
|
|
for b.Loop() {
|
|
if err := json.API.NewEncoder(io.Discard).Encode(nested); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkDecoder(b *testing.B) {
|
|
r := strings.NewReader("")
|
|
for b.Loop() {
|
|
r.Reset(string(nestedJSON))
|
|
|
|
var out benchPayload
|
|
if err := json.API.NewDecoder(r).Decode(&out); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|