Skip to content

Commit c5f251b

Browse files
author
junoberryferry
committed
use experimental go json v2 library
1 parent dca375a commit c5f251b

File tree

5 files changed

+161
-3
lines changed

5 files changed

+161
-3
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ DIST := dist
1818
DIST_DIRS := $(DIST)/binaries $(DIST)/release
1919
IMPORT := code.gitea.io/gitea
2020

21+
# No experiment set by default, but you can set jsonv2 to use go 1.25.0 json v2 experimental changes
22+
export GOEXPERIMENT ?=
23+
2124
GO ?= go
2225
SHASUM ?= shasum -a 256
2326
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module code.gitea.io/gitea
22

3-
go 1.24.6
3+
go 1.25
44

55
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
66
// But some CAs use negative serial number, just relax the check. related:

modules/json/json.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,22 @@ type Interface interface {
3232
}
3333

3434
var (
35-
// DefaultJSONHandler default json handler
36-
DefaultJSONHandler Interface = JSONiter{jsoniter.ConfigCompatibleWithStandardLibrary}
35+
// DefaultJSONHandler default json handler - uses JSON v2 if available, otherwise JSONiter
36+
DefaultJSONHandler = getDefaultHandler()
3737

3838
_ Interface = StdJSON{}
3939
_ Interface = JSONiter{}
40+
_ Interface = JSONv2{}
4041
)
4142

43+
// getDefaultHandler returns the expected JSON implementation
44+
func getDefaultHandler() Interface {
45+
if isJSONv2Available() {
46+
return JSONv2{}
47+
}
48+
return JSONiter{jsoniter.ConfigCompatibleWithStandardLibrary}
49+
}
50+
4251
// StdJSON implements Interface via encoding/json
4352
type StdJSON struct{}
4453

@@ -97,6 +106,47 @@ func (j JSONiter) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) e
97106
return json.Indent(dst, src, prefix, indent)
98107
}
99108

109+
// JSONv2 implements Interface via encoding/json/v2
110+
// Requires GOEXPERIMENT=jsonv2 to be set at build time
111+
type JSONv2 struct{}
112+
113+
// Marshal implements Interface using JSON v2 - fallback if v2 is not available
114+
func (JSONv2) Marshal(v any) ([]byte, error) {
115+
if !isJSONv2Available() {
116+
return json.Marshal(v)
117+
}
118+
return marshalV2(v)
119+
}
120+
121+
// Unmarshal implements Interface using JSON v2 - fallback if v2 is not available
122+
func (JSONv2) Unmarshal(data []byte, v any) error {
123+
if !isJSONv2Available() {
124+
return json.Unmarshal(data, v)
125+
}
126+
return unmarshalV2(data, v)
127+
}
128+
129+
// NewEncoder implements Interface using JSON v2 - fallback if v2 is not available
130+
func (JSONv2) NewEncoder(writer io.Writer) Encoder {
131+
if !isJSONv2Available() {
132+
return json.NewEncoder(writer)
133+
}
134+
return newEncoderV2(writer)
135+
}
136+
137+
// NewDecoder implements Interface using JSON v2 - fallback if v2 is not available
138+
func (JSONv2) NewDecoder(reader io.Reader) Decoder {
139+
if !isJSONv2Available() {
140+
return json.NewDecoder(reader)
141+
}
142+
return newDecoderV2(reader)
143+
}
144+
145+
// Indent implements Interface using standard library (JSON v2 doesn't have Indent yet)
146+
func (JSONv2) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
147+
return json.Indent(dst, src, prefix, indent)
148+
}
149+
100150
// Marshal converts object as bytes
101151
func Marshal(v any) ([]byte, error) {
102152
return DefaultJSONHandler.Marshal(v)

modules/json/jsonv2.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//go:build goexperiment.jsonv2
2+
3+
// Copyright 2025 The Gitea Authors. All rights reserved.
4+
// SPDX-License-Identifier: MIT
5+
6+
package json
7+
8+
import (
9+
jsonv2 "encoding/json/v2"
10+
"io"
11+
)
12+
13+
// isJSONv2Available returns true when JSON v2 is available (compiled with GOEXPERIMENT=jsonv2)
14+
func isJSONv2Available() bool {
15+
return true
16+
}
17+
18+
// marshalV2 uses JSON v2 marshal with v1 compatibility options
19+
func marshalV2(v any) ([]byte, error) {
20+
opts := jsonv2.JoinOptions(
21+
jsonv2.MatchCaseInsensitiveNames(true),
22+
jsonv2.FormatNilSliceAsNull(true),
23+
jsonv2.FormatNilMapAsNull(true),
24+
)
25+
return jsonv2.Marshal(v, opts)
26+
}
27+
28+
// unmarshalV2 uses JSON v2 unmarshal with v1 compatibility options
29+
func unmarshalV2(data []byte, v any) error {
30+
opts := jsonv2.JoinOptions(
31+
jsonv2.MatchCaseInsensitiveNames(true),
32+
)
33+
return jsonv2.Unmarshal(data, v, opts)
34+
}
35+
36+
// encoderV2 wraps JSON v2 streaming encoder
37+
type encoderV2 struct {
38+
writer io.Writer
39+
opts jsonv2.Options
40+
}
41+
42+
func (e *encoderV2) Encode(v any) error {
43+
return jsonv2.MarshalWrite(e.writer, v, e.opts)
44+
}
45+
46+
// newEncoderV2 creates a new JSON v2 streaming encoder
47+
func newEncoderV2(writer io.Writer) Encoder {
48+
opts := jsonv2.JoinOptions(
49+
jsonv2.MatchCaseInsensitiveNames(true),
50+
jsonv2.FormatNilSliceAsNull(true),
51+
jsonv2.FormatNilMapAsNull(true),
52+
)
53+
return &encoderV2{writer: writer, opts: opts}
54+
}
55+
56+
// decoderV2 wraps JSON v2 streaming decoder
57+
type decoderV2 struct {
58+
reader io.Reader
59+
opts jsonv2.Options
60+
}
61+
62+
func (d *decoderV2) Decode(v any) error {
63+
return jsonv2.UnmarshalRead(d.reader, v, d.opts)
64+
}
65+
66+
// newDecoderV2 creates a new JSON v2 streaming decoder
67+
func newDecoderV2(reader io.Reader) Decoder {
68+
opts := jsonv2.JoinOptions(
69+
jsonv2.MatchCaseInsensitiveNames(true),
70+
)
71+
return &decoderV2{reader: reader, opts: opts}
72+
}

modules/json/jsonv2_fallback.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//go:build !goexperiment.jsonv2
2+
3+
// Copyright 2025 The Gitea Authors. All rights reserved.
4+
// SPDX-License-Identifier: MIT
5+
6+
package json
7+
8+
import "io"
9+
10+
// isJSONv2Available returns false when JSON v2 is not available (not compiled with GOEXPERIMENT=jsonv2)
11+
func isJSONv2Available() bool {
12+
return false
13+
}
14+
15+
// marshalV2 fallback - should not be called when JSON v2 is not available
16+
func marshalV2(v any) ([]byte, error) {
17+
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
18+
}
19+
20+
// unmarshalV2 fallback - should not be called when JSON v2 is not available
21+
func unmarshalV2(data []byte, v any) error {
22+
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
23+
}
24+
25+
// newEncoderV2 fallback - should not be called when JSON v2 is not available
26+
func newEncoderV2(writer io.Writer) Encoder {
27+
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
28+
}
29+
30+
// newDecoderV2 fallback - should not be called when JSON v2 is not available
31+
func newDecoderV2(reader io.Reader) Decoder {
32+
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
33+
}

0 commit comments

Comments
 (0)