初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
|
||||
// Encoder represents an encoder for json
|
||||
type Encoder interface {
|
||||
Encode(v any) error
|
||||
}
|
||||
|
||||
// Decoder represents a decoder for json
|
||||
type Decoder interface {
|
||||
Decode(v any) error
|
||||
}
|
||||
|
||||
// Interface represents an interface to handle json data
|
||||
type Interface interface {
|
||||
Marshal(v any) ([]byte, error)
|
||||
Unmarshal(data []byte, v any) error
|
||||
NewEncoder(writer io.Writer) Encoder
|
||||
NewDecoder(reader io.Reader) Decoder
|
||||
Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error
|
||||
}
|
||||
|
||||
var DefaultJSONHandler = getDefaultJSONHandler()
|
||||
|
||||
// Marshal converts object as bytes
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
return DefaultJSONHandler.Marshal(v)
|
||||
}
|
||||
|
||||
// Unmarshal decodes object from bytes
|
||||
func Unmarshal(data []byte, v any) error {
|
||||
return DefaultJSONHandler.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// NewEncoder creates an encoder to write objects to writer
|
||||
func NewEncoder(writer io.Writer) Encoder {
|
||||
return DefaultJSONHandler.NewEncoder(writer)
|
||||
}
|
||||
|
||||
// NewDecoder creates a decoder to read objects from reader
|
||||
func NewDecoder(reader io.Reader) Decoder {
|
||||
return DefaultJSONHandler.NewDecoder(reader)
|
||||
}
|
||||
|
||||
// Indent appends to dst an indented form of the JSON-encoded src.
|
||||
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
return DefaultJSONHandler.Indent(dst, src, prefix, indent)
|
||||
}
|
||||
|
||||
// MarshalIndent copied from encoding/json
|
||||
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||
b, err := Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = Indent(&buf, b, prefix, indent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Valid proxy to json.Valid
|
||||
func Valid(data []byte) bool {
|
||||
return json.Valid(data)
|
||||
}
|
||||
|
||||
// UnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
|
||||
// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
|
||||
func UnmarshalHandleDoubleEncode(bs []byte, v any) error {
|
||||
if len(bs) == 0 {
|
||||
// json.Unmarshal will report errors if input is empty (nil or zero-length)
|
||||
// It seems that XORM ignores the nil but still passes zero-length string into this function
|
||||
// To be consistent, we should treat all empty inputs as success
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal(bs, v)
|
||||
if err != nil {
|
||||
ok := true
|
||||
rs := []byte{}
|
||||
temp := make([]byte, 2)
|
||||
for _, rn := range string(bs) {
|
||||
if rn > 0xffff {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
binary.LittleEndian.PutUint16(temp, uint16(rn))
|
||||
rs = append(rs, temp...)
|
||||
}
|
||||
if ok {
|
||||
if len(rs) > 1 && rs[0] == 0xff && rs[1] == 0xfe {
|
||||
rs = rs[2:]
|
||||
}
|
||||
err = json.Unmarshal(rs, v)
|
||||
}
|
||||
}
|
||||
if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
|
||||
err = json.Unmarshal(bs[2:], v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGiteaDBJSONUnmarshal(t *testing.T) {
|
||||
var m map[any]any
|
||||
err := UnmarshalHandleDoubleEncode(nil, &m)
|
||||
assert.NoError(t, err)
|
||||
err = UnmarshalHandleDoubleEncode([]byte(""), &m)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIndent(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := Indent(buf, []byte(`{"a":1}`), ">", " ")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{
|
||||
> "a": 1
|
||||
>}`, buf.String())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
|
||||
func getDefaultJSONHandler() Interface {
|
||||
return jsonV1{}
|
||||
}
|
||||
|
||||
func MarshalKeepOptionalEmpty(v any) ([]byte, error) {
|
||||
return DefaultJSONHandler.Marshal(v)
|
||||
}
|
||||
|
||||
func NewDecoderCaseInsensitive(reader io.Reader) Decoder {
|
||||
return DefaultJSONHandler.NewDecoder(reader)
|
||||
}
|
||||
|
||||
type Value = json.RawMessage
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
|
||||
type jsonV1 struct{}
|
||||
|
||||
var _ Interface = jsonV1{}
|
||||
|
||||
func (jsonV1) Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
func (jsonV1) Unmarshal(data []byte, v any) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func (jsonV1) NewEncoder(writer io.Writer) Encoder {
|
||||
return json.NewEncoder(writer)
|
||||
}
|
||||
|
||||
func (jsonV1) NewDecoder(reader io.Reader) Decoder {
|
||||
return json.NewDecoder(reader)
|
||||
}
|
||||
|
||||
func (jsonV1) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
return json.Indent(dst, src, prefix, indent)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
jsonv1 "encoding/json" //nolint:depguard // this package wraps it
|
||||
"encoding/json/jsontext" //nolint:depguard // this package wraps it
|
||||
jsonv2 "encoding/json/v2" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
|
||||
// JSONv2 implements Interface via encoding/json/v2
|
||||
// Requires GOEXPERIMENT=jsonv2 to be set at build time
|
||||
type JSONv2 struct {
|
||||
marshalOptions jsonv2.Options
|
||||
marshalKeepOptionalEmptyOptions jsonv2.Options
|
||||
unmarshalOptions jsonv2.Options
|
||||
unmarshalCaseInsensitiveOptions jsonv2.Options
|
||||
}
|
||||
|
||||
var jsonV2 JSONv2
|
||||
|
||||
func init() {
|
||||
commonMarshalOptions := []jsonv2.Options{
|
||||
jsonv2.FormatNilSliceAsNull(true),
|
||||
jsonv2.FormatNilMapAsNull(true),
|
||||
}
|
||||
jsonV2.marshalOptions = jsonv2.JoinOptions(commonMarshalOptions...)
|
||||
jsonV2.unmarshalOptions = jsonv2.DefaultOptionsV2()
|
||||
|
||||
// By default, "json/v2" omitempty removes all `""` empty strings, no matter where it comes from.
|
||||
// v1 has a different behavior: if the `""` is from a null pointer, or a Marshal function, it is kept.
|
||||
// Golang issue: https://github.com/golang/go/issues/75623 encoding/json/v2: unable to make omitempty work with pointer or Optional type with goexperiment.jsonv2
|
||||
jsonV2.marshalKeepOptionalEmptyOptions = jsonv2.JoinOptions(append(commonMarshalOptions, jsonv1.OmitEmptyWithLegacySemantics(true))...)
|
||||
|
||||
// Some legacy code uses case-insensitive matching (for example: parsing oci.ImageConfig)
|
||||
jsonV2.unmarshalCaseInsensitiveOptions = jsonv2.JoinOptions(jsonv2.MatchCaseInsensitiveNames(true))
|
||||
}
|
||||
|
||||
func getDefaultJSONHandler() Interface {
|
||||
return &jsonV2
|
||||
}
|
||||
|
||||
func MarshalKeepOptionalEmpty(v any) ([]byte, error) {
|
||||
return jsonv2.Marshal(v, jsonV2.marshalKeepOptionalEmptyOptions)
|
||||
}
|
||||
|
||||
func (j *JSONv2) Marshal(v any) ([]byte, error) {
|
||||
return jsonv2.Marshal(v, j.marshalOptions)
|
||||
}
|
||||
|
||||
func (j *JSONv2) Unmarshal(data []byte, v any) error {
|
||||
return jsonv2.Unmarshal(data, v, j.unmarshalOptions)
|
||||
}
|
||||
|
||||
func (j *JSONv2) NewEncoder(writer io.Writer) Encoder {
|
||||
return &jsonV2Encoder{writer: writer, opts: j.marshalOptions}
|
||||
}
|
||||
|
||||
func (j *JSONv2) NewDecoder(reader io.Reader) Decoder {
|
||||
return &jsonV2Decoder{reader: reader, opts: j.unmarshalOptions}
|
||||
}
|
||||
|
||||
// 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 jsonv1.Indent(dst, src, prefix, indent)
|
||||
}
|
||||
|
||||
type jsonV2Encoder struct {
|
||||
writer io.Writer
|
||||
opts jsonv2.Options
|
||||
}
|
||||
|
||||
func (e *jsonV2Encoder) Encode(v any) error {
|
||||
return jsonv2.MarshalWrite(e.writer, v, e.opts)
|
||||
}
|
||||
|
||||
type jsonV2Decoder struct {
|
||||
reader io.Reader
|
||||
opts jsonv2.Options
|
||||
}
|
||||
|
||||
func (d *jsonV2Decoder) Decode(v any) error {
|
||||
return jsonv2.UnmarshalRead(d.reader, v, d.opts)
|
||||
}
|
||||
|
||||
func NewDecoderCaseInsensitive(reader io.Reader) Decoder {
|
||||
return &jsonV2Decoder{reader: reader, opts: jsonV2.unmarshalCaseInsensitiveOptions}
|
||||
}
|
||||
|
||||
type Value = jsontext.Value
|
||||
Reference in New Issue
Block a user