mirror of https://github.com/gin-gonic/gin.git
Compare commits
3 Commits
525e4354a4
...
eeaa150f35
Author | SHA1 | Date |
---|---|---|
TimAndy | eeaa150f35 | |
TimAndy | 671bb17bd9 | |
TimAndy | 973c555810 |
|
@ -13,8 +13,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -270,9 +270,9 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
||||||
case multipart.FileHeader:
|
case multipart.FileHeader:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.Api.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.Api.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if !value.Elem().IsValid() {
|
if !value.Elem().IsValid() {
|
||||||
value.Set(reflect.New(value.Type().Elem()))
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
|
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
|
||||||
|
@ -42,7 +42,7 @@ func (jsonBinding) BindBody(body []byte, obj any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeJSON(r io.Reader, obj any) error {
|
func decodeJSON(r io.Reader, obj any) error {
|
||||||
decoder := json.NewDecoder(r)
|
decoder := json.Api.NewDecoder(r)
|
||||||
if EnableDecoderUseNumber {
|
if EnableDecoderUseNumber {
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,17 @@
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/codec/api"
|
||||||
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
|
"github.com/gin-gonic/gin/render"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -28,3 +37,184 @@ func TestJSONBindingBindBodyMap(t *testing.T) {
|
||||||
assert.Equal(t, "FOO", s["foo"])
|
assert.Equal(t, "FOO", s["foo"])
|
||||||
assert.Equal(t, "world", s["hello"])
|
assert.Equal(t, "world", s["hello"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomJsonCodec(t *testing.T) {
|
||||||
|
//Restore json encoding configuration after testing
|
||||||
|
oldMarshal := json.Api
|
||||||
|
defer func() {
|
||||||
|
json.Api = oldMarshal
|
||||||
|
}()
|
||||||
|
//Custom json api
|
||||||
|
json.Api = customJsonApi{}
|
||||||
|
|
||||||
|
//test decode json
|
||||||
|
obj := customReq{}
|
||||||
|
err := jsonBinding{}.BindBody([]byte(`{"time_empty":null,"time_struct": "2001-12-05 10:01:02.345","time_nil":null,"time_pointer":"2002-12-05 10:01:02.345"}`), &obj)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, zeroTime, obj.TimeEmpty)
|
||||||
|
assert.Equal(t, time.Date(2001, 12, 05, 10, 01, 02, 345000000, time.Local), obj.TimeStruct)
|
||||||
|
assert.Nil(t, obj.TimeNil)
|
||||||
|
assert.Equal(t, time.Date(2002, 12, 05, 10, 01, 02, 345000000, time.Local), *obj.TimePointer)
|
||||||
|
//test encode json
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
err2 := (render.PureJSON{Data: obj}).Render(w)
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
assert.Equal(t, "{\"time_empty\":null,\"time_struct\":\"2001-12-05 10:01:02.345\",\"time_nil\":null,\"time_pointer\":\"2002-12-05 10:01:02.345\"}\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
type customReq struct {
|
||||||
|
TimeEmpty time.Time `json:"time_empty"`
|
||||||
|
TimeStruct time.Time `json:"time_struct"`
|
||||||
|
TimeNil *time.Time `json:"time_nil"`
|
||||||
|
TimePointer *time.Time `json:"time_pointer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var customConfig = jsoniter.Config{
|
||||||
|
EscapeHTML: true,
|
||||||
|
SortMapKeys: true,
|
||||||
|
ValidateJsonRawMessage: true,
|
||||||
|
}.Froze()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
customConfig.RegisterExtension(&TimeEx{})
|
||||||
|
customConfig.RegisterExtension(&TimePointerEx{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type customJsonApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) Marshal(v any) ([]byte, error) {
|
||||||
|
return customConfig.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) Unmarshal(data []byte, v any) error {
|
||||||
|
return customConfig.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||||
|
return customConfig.MarshalIndent(v, prefix, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) NewEncoder(writer io.Writer) api.JsonEncoder {
|
||||||
|
return customConfig.NewEncoder(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) NewDecoder(reader io.Reader) api.JsonDecoder {
|
||||||
|
return customConfig.NewDecoder(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
//region Time Extension
|
||||||
|
|
||||||
|
var (
|
||||||
|
zeroTime = time.Time{}
|
||||||
|
timeType = reflect2.TypeOfPtr((*time.Time)(nil)).Elem()
|
||||||
|
defaultTimeCodec = &timeCodec{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeEx struct {
|
||||||
|
jsoniter.DummyExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *TimeEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {
|
||||||
|
if typ == timeType {
|
||||||
|
return defaultTimeCodec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *TimeEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
|
||||||
|
if typ == timeType {
|
||||||
|
return defaultTimeCodec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc timeCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
t := *((*time.Time)(ptr))
|
||||||
|
return t == zeroTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc timeCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
|
t := *((*time.Time)(ptr))
|
||||||
|
if t == zeroTime {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.WriteString(t.In(time.Local).Format("2006-01-02 15:04:05.000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc timeCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||||
|
ts := iter.ReadString()
|
||||||
|
if len(ts) == 0 {
|
||||||
|
*((*time.Time)(ptr)) = zeroTime
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err := time.ParseInLocation("2006-01-02 15:04:05.000", ts, time.Local)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
*((*time.Time)(ptr)) = t
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region *Time Extension
|
||||||
|
|
||||||
|
var (
|
||||||
|
timePointerType = reflect2.TypeOfPtr((**time.Time)(nil)).Elem()
|
||||||
|
defaultTimePointerCodec = &timePointerCodec{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimePointerEx struct {
|
||||||
|
jsoniter.DummyExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpe *TimePointerEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {
|
||||||
|
if typ == timePointerType {
|
||||||
|
return defaultTimePointerCodec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpe *TimePointerEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
|
||||||
|
if typ == timePointerType {
|
||||||
|
return defaultTimePointerCodec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type timePointerCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpc timePointerCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
t := *((**time.Time)(ptr))
|
||||||
|
return t == nil || *t == zeroTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpc timePointerCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
|
t := *((**time.Time)(ptr))
|
||||||
|
if t == nil || *t == zeroTime {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.WriteString(t.In(time.Local).Format("2006-01-02 15:04:05.000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpc timePointerCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||||
|
ts := iter.ReadString()
|
||||||
|
if len(ts) == 0 {
|
||||||
|
*((**time.Time)(ptr)) = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err := time.ParseInLocation("2006-01-02 15:04:05.000", ts, time.Local)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
*((**time.Time)(ptr)) = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2022 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 api
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// JsonApi the api for json codec.
|
||||||
|
type JsonApi interface {
|
||||||
|
Marshal(v any) ([]byte, error)
|
||||||
|
Unmarshal(data []byte, v any) error
|
||||||
|
MarshalIndent(v any, prefix, indent string) ([]byte, error)
|
||||||
|
NewEncoder(writer io.Writer) JsonEncoder
|
||||||
|
NewDecoder(reader io.Reader) JsonDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// A JsonEncoder interface writes JSON values to an output stream.
|
||||||
|
type JsonEncoder interface {
|
||||||
|
// SetEscapeHTML specifies whether problematic HTML characters
|
||||||
|
// should be escaped inside JSON quoted strings.
|
||||||
|
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
|
||||||
|
// to avoid certain safety problems that can arise when embedding JSON in HTML.
|
||||||
|
//
|
||||||
|
// In non-HTML settings where the escaping interferes with the readability
|
||||||
|
// of the output, SetEscapeHTML(false) disables this behavior.
|
||||||
|
SetEscapeHTML(on bool)
|
||||||
|
|
||||||
|
// Encode writes the JSON encoding of v to the stream,
|
||||||
|
// followed by a newline character.
|
||||||
|
//
|
||||||
|
// See the documentation for Marshal for details about the
|
||||||
|
// conversion of Go values to JSON.
|
||||||
|
Encode(v interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A JsonDecoder interface reads and decodes JSON values from an input stream.
|
||||||
|
type JsonDecoder interface {
|
||||||
|
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
|
// Number instead of as a float64.
|
||||||
|
UseNumber()
|
||||||
|
|
||||||
|
// DisallowUnknownFields causes the Decoder to return an error when the destination
|
||||||
|
// is a struct and the input contains object keys which do not match any
|
||||||
|
// non-ignored, exported fields in the destination.
|
||||||
|
DisallowUnknownFields()
|
||||||
|
|
||||||
|
// Decode reads the next JSON-encoded value from its
|
||||||
|
// input and stores it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// See the documentation for Unmarshal for details about
|
||||||
|
// the conversion of JSON into a Go value.
|
||||||
|
Decode(v interface{}) error
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin/codec/api"
|
||||||
|
|
||||||
|
var Api api.JsonApi
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go_json
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/codec/api"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Api = gojsonApi{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gojsonApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j gojsonApi) Marshal(v any) ([]byte, error) {
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j gojsonApi) Unmarshal(data []byte, v any) error {
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j gojsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||||
|
return json.MarshalIndent(v, prefix, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j gojsonApi) NewEncoder(writer io.Writer) api.JsonEncoder {
|
||||||
|
return json.NewEncoder(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j gojsonApi) NewDecoder(reader io.Reader) api.JsonDecoder {
|
||||||
|
return json.NewDecoder(reader)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64)
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/codec/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Api = jsonApi{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonApi) Marshal(v any) ([]byte, error) {
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonApi) Unmarshal(data []byte, v any) error {
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||||
|
return json.MarshalIndent(v, prefix, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonApi) NewEncoder(writer io.Writer) api.JsonEncoder {
|
||||||
|
return json.NewEncoder(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonApi) NewDecoder(reader io.Reader) api.JsonDecoder {
|
||||||
|
return json.NewDecoder(reader)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build jsoniter
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/codec/api"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Api = jsoniterApi{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
|
||||||
|
type jsoniterApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsoniterApi) Marshal(v any) ([]byte, error) {
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsoniterApi) Unmarshal(data []byte, v any) error {
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsoniterApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||||
|
return json.MarshalIndent(v, prefix, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsoniterApi) NewEncoder(writer io.Writer) api.JsonEncoder {
|
||||||
|
return json.NewEncoder(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsoniterApi) NewDecoder(reader io.Reader) api.JsonDecoder {
|
||||||
|
return json.NewDecoder(reader)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
//go:build sonic && avx && (linux || windows || darwin) && amd64
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/bytedance/sonic"
|
||||||
|
"github.com/gin-gonic/gin/codec/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Api = sonicApi{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = sonic.ConfigStd
|
||||||
|
|
||||||
|
type sonicApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j sonicApi) Marshal(v any) ([]byte, error) {
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j sonicApi) Unmarshal(data []byte, v any) error {
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j sonicApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||||
|
return json.MarshalIndent(v, prefix, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j sonicApi) NewEncoder(writer io.Writer) api.JsonEncoder {
|
||||||
|
return json.NewEncoder(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j sonicApi) NewDecoder(reader io.Reader) api.JsonDecoder {
|
||||||
|
return json.NewDecoder(reader)
|
||||||
|
}
|
61
docs/doc.md
61
docs/doc.md
|
@ -61,6 +61,7 @@
|
||||||
- [http2 server push](#http2-server-push)
|
- [http2 server push](#http2-server-push)
|
||||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
- [Set and get a cookie](#set-and-get-a-cookie)
|
||||||
|
- [Custom json codec at runtime](#custom-json-codec-at-runtime)
|
||||||
- [Don't trust all proxies](#dont-trust-all-proxies)
|
- [Don't trust all proxies](#dont-trust-all-proxies)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
|
|
||||||
|
@ -2207,6 +2208,66 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom json codec at runtime
|
||||||
|
|
||||||
|
Gin support custom json serialization and deserialization logic without using compile tags.
|
||||||
|
|
||||||
|
1. Define a custom struct implements the `api.JsonApi` interface.
|
||||||
|
|
||||||
|
2. Before your engine starts, assign values to `json.Api` using the custom struct.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/codec/api"
|
||||||
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var customConfig = jsoniter.Config{
|
||||||
|
EscapeHTML: true,
|
||||||
|
SortMapKeys: true,
|
||||||
|
ValidateJsonRawMessage: true,
|
||||||
|
}.Froze()
|
||||||
|
|
||||||
|
// implement api.JsonApi
|
||||||
|
type customJsonApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) Marshal(v any) ([]byte, error) {
|
||||||
|
return customConfig.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) Unmarshal(data []byte, v any) error {
|
||||||
|
return customConfig.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||||
|
return customConfig.MarshalIndent(v, prefix, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) NewEncoder(writer io.Writer) api.JsonEncoder {
|
||||||
|
return customConfig.NewEncoder(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j customJsonApi) NewDecoder(reader io.Reader) api.JsonDecoder {
|
||||||
|
return customConfig.NewDecoder(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
//Replace the default json api
|
||||||
|
json.Api = customJsonApi{}
|
||||||
|
|
||||||
|
//Start your gin engine
|
||||||
|
router := gin.Default()
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Don't trust all proxies
|
## Don't trust all proxies
|
||||||
|
|
||||||
Gin lets you specify which headers to hold the real client IP (if any),
|
Gin lets you specify which headers to hold the real client IP (if any),
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
|
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
|
||||||
|
@ -77,7 +77,7 @@ func (msg *Error) JSON() any {
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaller interface.
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
func (msg *Error) MarshalJSON() ([]byte, error) {
|
func (msg *Error) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(msg.JSON())
|
return json.Api.Marshal(msg.JSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements the error interface.
|
// Error implements the error interface.
|
||||||
|
@ -157,7 +157,7 @@ func (a errorMsgs) JSON() any {
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaller interface.
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(a.JSON())
|
return json.Api.Marshal(a.JSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a errorMsgs) String() string {
|
func (a errorMsgs) String() string {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func TestError(t *testing.T) {
|
||||||
"meta": "some data",
|
"meta": "some data",
|
||||||
}, err.JSON())
|
}, err.JSON())
|
||||||
|
|
||||||
jsonBytes, _ := json.Marshal(err)
|
jsonBytes, _ := json.Api.Marshal(err)
|
||||||
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
||||||
|
|
||||||
err.SetMeta(H{ //nolint: errcheck
|
err.SetMeta(H{ //nolint: errcheck
|
||||||
|
@ -91,13 +91,13 @@ Error #03: third
|
||||||
H{"error": "second", "meta": "some data"},
|
H{"error": "second", "meta": "some data"},
|
||||||
H{"error": "third", "status": "400"},
|
H{"error": "third", "status": "400"},
|
||||||
}, errs.JSON())
|
}, errs.JSON())
|
||||||
jsonBytes, _ := json.Marshal(errs)
|
jsonBytes, _ := json.Api.Marshal(errs)
|
||||||
assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes))
|
assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes))
|
||||||
errs = errorMsgs{
|
errs = errorMsgs{
|
||||||
{Err: errors.New("first"), Type: ErrorTypePrivate},
|
{Err: errors.New("first"), Type: ErrorTypePrivate},
|
||||||
}
|
}
|
||||||
assert.Equal(t, H{"error": "first"}, errs.JSON())
|
assert.Equal(t, H{"error": "first"}, errs.JSON())
|
||||||
jsonBytes, _ = json.Marshal(errs)
|
jsonBytes, _ = json.Api.Marshal(errs)
|
||||||
assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes))
|
assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes))
|
||||||
|
|
||||||
errs = errorMsgs{}
|
errs = errorMsgs{}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
|
github.com/modern-go/reflect2 v1.0.2
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2
|
github.com/pelletier/go-toml/v2 v2.2.2
|
||||||
github.com/quic-go/quic-go v0.43.1
|
github.com/quic-go/quic-go v0.43.1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
|
@ -32,7 +33,6 @@ require (
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build go_json
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import json "github.com/goccy/go-json"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Marshal is exported by gin/json package.
|
|
||||||
Marshal = json.Marshal
|
|
||||||
// Unmarshal is exported by gin/json package.
|
|
||||||
Unmarshal = json.Unmarshal
|
|
||||||
// MarshalIndent is exported by gin/json package.
|
|
||||||
MarshalIndent = json.MarshalIndent
|
|
||||||
// NewDecoder is exported by gin/json package.
|
|
||||||
NewDecoder = json.NewDecoder
|
|
||||||
// NewEncoder is exported by gin/json package.
|
|
||||||
NewEncoder = json.NewEncoder
|
|
||||||
)
|
|
|
@ -1,22 +0,0 @@
|
||||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64)
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Marshal is exported by gin/json package.
|
|
||||||
Marshal = json.Marshal
|
|
||||||
// Unmarshal is exported by gin/json package.
|
|
||||||
Unmarshal = json.Unmarshal
|
|
||||||
// MarshalIndent is exported by gin/json package.
|
|
||||||
MarshalIndent = json.MarshalIndent
|
|
||||||
// NewDecoder is exported by gin/json package.
|
|
||||||
NewDecoder = json.NewDecoder
|
|
||||||
// NewEncoder is exported by gin/json package.
|
|
||||||
NewEncoder = json.NewEncoder
|
|
||||||
)
|
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build jsoniter
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import jsoniter "github.com/json-iterator/go"
|
|
||||||
|
|
||||||
var (
|
|
||||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
// Marshal is exported by gin/json package.
|
|
||||||
Marshal = json.Marshal
|
|
||||||
// Unmarshal is exported by gin/json package.
|
|
||||||
Unmarshal = json.Unmarshal
|
|
||||||
// MarshalIndent is exported by gin/json package.
|
|
||||||
MarshalIndent = json.MarshalIndent
|
|
||||||
// NewDecoder is exported by gin/json package.
|
|
||||||
NewDecoder = json.NewDecoder
|
|
||||||
// NewEncoder is exported by gin/json package.
|
|
||||||
NewEncoder = json.NewEncoder
|
|
||||||
)
|
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright 2022 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.
|
|
||||||
|
|
||||||
//go:build sonic && avx && (linux || windows || darwin) && amd64
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import "github.com/bytedance/sonic"
|
|
||||||
|
|
||||||
var (
|
|
||||||
json = sonic.ConfigStd
|
|
||||||
// Marshal is exported by gin/json package.
|
|
||||||
Marshal = json.Marshal
|
|
||||||
// Unmarshal is exported by gin/json package.
|
|
||||||
Unmarshal = json.Unmarshal
|
|
||||||
// MarshalIndent is exported by gin/json package.
|
|
||||||
MarshalIndent = json.MarshalIndent
|
|
||||||
// NewDecoder is exported by gin/json package.
|
|
||||||
NewDecoder = json.NewDecoder
|
|
||||||
// NewEncoder is exported by gin/json package.
|
|
||||||
NewEncoder = json.NewEncoder
|
|
||||||
)
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// JSON contains the given interface object.
|
// JSON contains the given interface object.
|
||||||
|
@ -65,7 +65,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
|
||||||
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||||
func WriteJSON(w http.ResponseWriter, obj any) error {
|
func WriteJSON(w http.ResponseWriter, obj any) error {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
jsonBytes, err := json.Marshal(obj)
|
jsonBytes, err := json.Api.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func WriteJSON(w http.ResponseWriter, obj any) error {
|
||||||
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
|
jsonBytes, err := json.Api.MarshalIndent(r.Data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
|
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
jsonBytes, err := json.Marshal(r.Data)
|
jsonBytes, err := json.Api.Marshal(r.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.
|
// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.
|
||||||
func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
ret, err := json.Marshal(r.Data)
|
ret, err := json.Api.Marshal(r.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
|
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
ret, err := json.Marshal(r.Data)
|
ret, err := json.Api.Marshal(r.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||||
func (r PureJSON) Render(w http.ResponseWriter) error {
|
func (r PureJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.Api.NewEncoder(w)
|
||||||
encoder.SetEscapeHTML(false)
|
encoder.SetEscapeHTML(false)
|
||||||
return encoder.Encode(r.Data)
|
return encoder.Encode(r.Data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
"github.com/gin-gonic/gin/codec/json"
|
||||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
@ -172,7 +172,7 @@ func TestRenderJsonpJSONError(t *testing.T) {
|
||||||
err = jsonpJSON.Render(ew)
|
err = jsonpJSON.Render(ew)
|
||||||
assert.Equal(t, `write "`+`(`+`" error`, err.Error())
|
assert.Equal(t, `write "`+`(`+`" error`, err.Error())
|
||||||
|
|
||||||
data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data
|
data, _ := json.Api.Marshal(jsonpJSON.Data) // error was returned while writing data
|
||||||
ew.bufString = string(data)
|
ew.bufString = string(data)
|
||||||
err = jsonpJSON.Render(ew)
|
err = jsonpJSON.Render(ew)
|
||||||
assert.Equal(t, `write "`+string(data)+`" error`, err.Error())
|
assert.Equal(t, `write "`+string(data)+`" error`, err.Error())
|
||||||
|
|
Loading…
Reference in New Issue