Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c5f251b
use experimental go json v2 library
Aug 31, 2025
cb16b33
Merge branch 'main' into jsonv2
techknowlogick Sep 1, 2025
abc734c
Update go.mod
techknowlogick Sep 2, 2025
f9c8805
Update jsonv2.go
techknowlogick Sep 2, 2025
9d83440
Update jsonv2.go
techknowlogick Sep 2, 2025
31492c1
Update jsonv2.go
techknowlogick Sep 2, 2025
7b0832c
Update jsonv2.go
techknowlogick Sep 2, 2025
fe51938
Update jsonv2_fallback.go
techknowlogick Sep 2, 2025
29d1770
Update jsonv2_fallback.go
techknowlogick Sep 2, 2025
3c3f77f
Update jsonv2_fallback.go
techknowlogick Sep 2, 2025
ce4107d
Merge branch 'main' into jsonv2
techknowlogick Sep 2, 2025
b8fb94d
Update jsonv2_fallback.go
techknowlogick Sep 3, 2025
c748ca8
Update jsonv2_fallback.go
techknowlogick Sep 3, 2025
f9d91f2
Merge branch 'main' into jsonv2
techknowlogick Sep 4, 2025
7fec838
Update Go version from 1.24.6 to 1.25.0
techknowlogick Sep 4, 2025
3c6d690
bump go.mod
techknowlogick Sep 4, 2025
b3969be
use fixed go-swagger version
techknowlogick Sep 4, 2025
ba43047
Merge branch 'main' into jsonv2
techknowlogick Sep 4, 2025
94d537c
Update GOEXPERIMENT to use jsonv2 by default
techknowlogick Sep 4, 2025
82b2d97
fix lint
techknowlogick Sep 4, 2025
502b4fb
clean up modernizer fixes
techknowlogick Sep 4, 2025
d4b1e10
try to fix lint
techknowlogick Sep 4, 2025
8f3b218
try to use go1.25 waitgroup logic
techknowlogick Sep 4, 2025
34af20c
fixup test fails
techknowlogick Sep 4, 2025
a65bff4
Merge remote-tracking branch 'upstream/main' into jsonv2
Sep 10, 2025
cdcb9bd
adjust jsonv2 output to become similar to v1
Sep 10, 2025
ecc304a
resolve vagrant test failure
Sep 11, 2025
748b590
the security check has a panic when using the experimental go library
Sep 11, 2025
60b26b0
resolve panic in assetfs parsing of embeded data
Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ DIST := dist
DIST_DIRS := $(DIST)/binaries $(DIST)/release
IMPORT := code.gitea.io/gitea

# No experiment set by default, but you can set jsonv2 to use go 1.25.0 json v2 experimental changes
export GOEXPERIMENT ?=

GO ?= go
SHASUM ?= shasum -a 256
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
Expand Down
54 changes: 52 additions & 2 deletions modules/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,22 @@ type Interface interface {
}

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

_ Interface = StdJSON{}
_ Interface = JSONiter{}
_ Interface = JSONv2{}
)

// getDefaultHandler returns the expected JSON implementation
func getDefaultHandler() Interface {
if isJSONv2Available() {
return JSONv2{}
}
return JSONiter{jsoniter.ConfigCompatibleWithStandardLibrary}
}

// StdJSON implements Interface via encoding/json
type StdJSON struct{}

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

// JSONv2 implements Interface via encoding/json/v2
// Requires GOEXPERIMENT=jsonv2 to be set at build time
type JSONv2 struct{}

// Marshal implements Interface using JSON v2 - fallback if v2 is not available
func (JSONv2) Marshal(v any) ([]byte, error) {
if !isJSONv2Available() {
return json.Marshal(v)
}
return marshalV2(v)
}

// Unmarshal implements Interface using JSON v2 - fallback if v2 is not available
func (JSONv2) Unmarshal(data []byte, v any) error {
if !isJSONv2Available() {
return json.Unmarshal(data, v)
}
return unmarshalV2(data, v)
}

// NewEncoder implements Interface using JSON v2 - fallback if v2 is not available
func (JSONv2) NewEncoder(writer io.Writer) Encoder {
if !isJSONv2Available() {
return json.NewEncoder(writer)
}
return newEncoderV2(writer)
}

// NewDecoder implements Interface using JSON v2 - fallback if v2 is not available
func (JSONv2) NewDecoder(reader io.Reader) Decoder {
if !isJSONv2Available() {
return json.NewDecoder(reader)
}
return newDecoderV2(reader)
}

// Indent implements Interface using standard library (JSON v2 doesn't have Indent yet)
func (JSONv2) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
return json.Indent(dst, src, prefix, indent)
}

// Marshal converts object as bytes
func Marshal(v any) ([]byte, error) {
return DefaultJSONHandler.Marshal(v)
Expand Down
70 changes: 70 additions & 0 deletions modules/json/jsonv2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package json

import (
jsonv2 "encoding/json/v2"

Check failure on line 7 in modules/json/jsonv2.go

View workflow job for this annotation

GitHub Actions / backend

package encoding/json/v2 is not in std (/opt/hostedtoolcache/go/1.24.6/x64/src/encoding/json/v2)
"io"
)

// isJSONv2Available returns true when JSON v2 is available (compiled with GOEXPERIMENT=jsonv2)
func isJSONv2Available() bool {
return true
}

// marshalV2 uses JSON v2 marshal with v1 compatibility options
func marshalV2(v any) ([]byte, error) {
opts := jsonv2.JoinOptions(
jsonv2.MatchCaseInsensitiveNames(true),
jsonv2.FormatNilSliceAsNull(true),
jsonv2.FormatNilMapAsNull(true),
)
return jsonv2.Marshal(v, opts)
}

// unmarshalV2 uses JSON v2 unmarshal with v1 compatibility options
func unmarshalV2(data []byte, v any) error {
opts := jsonv2.JoinOptions(
jsonv2.MatchCaseInsensitiveNames(true),
)
return jsonv2.Unmarshal(data, v, opts)
}

// encoderV2 wraps JSON v2 streaming encoder
type encoderV2 struct {
writer io.Writer
opts jsonv2.Options
}

func (e *encoderV2) Encode(v any) error {
return jsonv2.MarshalWrite(e.writer, v, e.opts)
}

// newEncoderV2 creates a new JSON v2 streaming encoder
func newEncoderV2(writer io.Writer) Encoder {
opts := jsonv2.JoinOptions(
jsonv2.MatchCaseInsensitiveNames(true),
jsonv2.FormatNilSliceAsNull(true),
jsonv2.FormatNilMapAsNull(true),
)
return &encoderV2{writer: writer, opts: opts}
}

// decoderV2 wraps JSON v2 streaming decoder
type decoderV2 struct {
reader io.Reader
opts jsonv2.Options
}

func (d *decoderV2) Decode(v any) error {
return jsonv2.UnmarshalRead(d.reader, v, d.opts)
}

// newDecoderV2 creates a new JSON v2 streaming decoder
func newDecoderV2(reader io.Reader) Decoder {
opts := jsonv2.JoinOptions(
jsonv2.MatchCaseInsensitiveNames(true),
)
return &decoderV2{reader: reader, opts: opts}
}
33 changes: 33 additions & 0 deletions modules/json/jsonv2_fallback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build !goexperiment.jsonv2

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package json

import "io"

// isJSONv2Available returns false when JSON v2 is not available (not compiled with GOEXPERIMENT=jsonv2)
func isJSONv2Available() bool {
return false
}

// marshalV2 fallback - should not be called when JSON v2 is not available
func marshalV2(v any) ([]byte, error) {
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
}

// unmarshalV2 fallback - should not be called when JSON v2 is not available
func unmarshalV2(data []byte, v any) error {
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
}

// newEncoderV2 fallback - should not be called when JSON v2 is not available
func newEncoderV2(writer io.Writer) Encoder {
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
}

// newDecoderV2 fallback - should not be called when JSON v2 is not available
func newDecoderV2(reader io.Reader) Decoder {
panic("JSON v2 not available - build with GOEXPERIMENT=jsonv2")
}
Loading