From 5e97e05654ea740ede884c224434547eb1e71aba Mon Sep 17 00:00:00 2001 From: mrredo <74524695+mrredo@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:08:38 +0300 Subject: [PATCH 1/7] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a46a8c936b..e3dea142fd 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/clarketm/json -go 1.15 +go 1.19 From 776a3d1365dc8e3ae83e33521b43a8c2139acd6b Mon Sep 17 00:00:00 2001 From: mrredo <74524695+mrredo@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:09:04 +0300 Subject: [PATCH 2/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37ae103f8e..e661bfebd2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # [json](https://godoc.org/github.com/clarketm/json) -> Mirrors [golang/go](https://github.com/golang/go) [![Golang version](https://img.shields.io/badge/go-1.15-green)](https://github.com/golang/go/releases/tag/go1.15) +> Mirrors [golang/go](https://github.com/golang/go) [![Golang version](https://img.shields.io/badge/go-1.19-green)](https://github.com/golang/go/releases/tag/go1.15) Drop-in replacement for Golang [`encoding/json`](https://golang.org/pkg/encoding/json/) with additional features. From 53a116c6d02f3cc2ae3559afbf8f78e650b61489 Mon Sep 17 00:00:00 2001 From: mrredo Date: Mon, 10 Oct 2022 19:16:46 +0300 Subject: [PATCH 3/7] i think i updated to 1.19 golang version --- bench_test.go | 136 +++++++++++++++++++++++++++- decode.go | 45 +++++----- decode_test.go | 110 +++++++++++------------ encode.go | 76 ++++++++-------- encode_test.go | 153 ++++++++++++++++++++------------ example_marshaling_test.go | 76 ++++++++-------- example_test.go | 4 +- example_text_marshaling_test.go | 76 ++++++++-------- fold.go | 12 ++- fold_test.go | 14 +-- fuzz_test.go | 83 +++++++++++++++++ go.mods | 3 + indent.go | 2 +- number_test.go | 17 +--- scanner.go | 6 +- scanner_test.go | 12 +-- stream.go | 15 ++-- stream_test.go | 131 ++++++++++++++++----------- tables.go | 2 +- tagkey_test.go | 8 +- tags.go | 18 ++-- tags_test.go | 2 +- 22 files changed, 627 insertions(+), 374 deletions(-) create mode 100644 fuzz_test.go create mode 100644 go.mods diff --git a/bench_test.go b/bench_test.go index 0d1b869142..729d319ec1 100644 --- a/bench_test.go +++ b/bench_test.go @@ -18,6 +18,7 @@ import ( "io" "os" "reflect" + "regexp" "runtime" "strings" "sync" @@ -99,6 +100,36 @@ func BenchmarkCodeEncoder(b *testing.B) { b.SetBytes(int64(len(codeJSON))) } +func BenchmarkCodeEncoderError(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + b.RunParallel(func(pb *testing.PB) { + enc := NewEncoder(io.Discard) + for pb.Next() { + if err := enc.Encode(&codeStruct); err != nil { + b.Fatal("Encode:", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("expect an error here") + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + func BenchmarkCodeMarshal(b *testing.B) { b.ReportAllocs() if codeJSON == nil { @@ -116,6 +147,35 @@ func BenchmarkCodeMarshal(b *testing.B) { b.SetBytes(int64(len(codeJSON))) } +func BenchmarkCodeMarshalError(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(&codeStruct); err != nil { + b.Fatal("Marshal:", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("expect an error here") + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + func benchMarshalBytes(n int) func(*testing.B) { sample := []byte("hello world") // Use a struct pointer, to avoid an allocation when passing it as an @@ -134,6 +194,36 @@ func benchMarshalBytes(n int) func(*testing.B) { } } +func benchMarshalBytesError(n int) func(*testing.B) { + sample := []byte("hello world") + // Use a struct pointer, to avoid an allocation when passing it as an + // interface parameter to Marshal. + v := &struct { + Bytes []byte + }{ + bytes.Repeat(sample, (n/len(sample))+1)[:n], + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := Marshal(v); err != nil { + b.Fatal("Marshal:", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("expect an error here") + } + } + } +} + func BenchmarkMarshalBytes(b *testing.B) { b.ReportAllocs() // 32 fits within encodeState.scratch. @@ -145,6 +235,17 @@ func BenchmarkMarshalBytes(b *testing.B) { b.Run("4096", benchMarshalBytes(4096)) } +func BenchmarkMarshalBytesError(b *testing.B) { + b.ReportAllocs() + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytesError(32)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytesError(256)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytesError(4096)) +} + func BenchmarkCodeDecoder(b *testing.B) { b.ReportAllocs() if codeJSON == nil { @@ -192,7 +293,7 @@ func BenchmarkDecoderStream(b *testing.B) { var buf bytes.Buffer dec := NewDecoder(&buf) buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") - var x interface{} + var x any if err := dec.Decode(&x); err != nil { b.Fatal("Decode:", err) } @@ -329,6 +430,9 @@ func BenchmarkUnmapped(b *testing.B) { func BenchmarkTypeFieldsCache(b *testing.B) { b.ReportAllocs() var maxTypes int = 1e6 + if testenv.Builder() != "" { + maxTypes = 1e3 // restrict cache sizes on builders + } // Dynamically generate many new types. types := make([]reflect.Type, maxTypes) @@ -405,3 +509,33 @@ func BenchmarkEncodeMarshaler(b *testing.B) { } }) } + +func BenchmarkEncoderEncode(b *testing.B) { + b.ReportAllocs() + type T struct { + X, Y string + } + v := &T{"foo", "bar"} + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := NewEncoder(io.Discard).Encode(v); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkNumberIsValid(b *testing.B) { + s := "-61657.61667E+61673" + for i := 0; i < b.N; i++ { + isValidNumber(s) + } +} + +func BenchmarkNumberIsValidRegexp(b *testing.B) { + var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) + s := "-61657.61667E+61673" + for i := 0; i < b.N; i++ { + jsonNumberRegexp.MatchString(s) + } +} \ No newline at end of file diff --git a/decode.go b/decode.go index a9917e72c7..0a8c113838 100644 --- a/decode.go +++ b/decode.go @@ -75,6 +75,8 @@ import ( // either be any string type, an integer, implement json.Unmarshaler, or // implement encoding.TextUnmarshaler. // +// If the JSON-encoded data contain a syntax error, Unmarshal returns a SyntaxError. +// // If a JSON value is not appropriate for a given target type, // or if a JSON number overflows the target type, Unmarshal // skips that field and completes the unmarshaling as best it can. @@ -85,15 +87,14 @@ import ( // // The JSON null value unmarshals into an interface, map, pointer, or slice // by setting that Go value to nil. Because null is often used in JSON to mean -// ``not present,'' unmarshaling a JSON null into any other Go type has no effect +// “not present,” unmarshaling a JSON null into any other Go type has no effect // on the value and produces no error. // // When unmarshaling quoted strings, invalid UTF-8 or // invalid UTF-16 surrogate pairs are not treated as an error. // Instead, they are replaced by the Unicode replacement // character U+FFFD. -// -func Unmarshal(data []byte, v interface{}) error { +func Unmarshal(data []byte, v any) error { // Check for well-formedness. // Avoids filling out half a data structure // before discovering a JSON syntax error. @@ -161,15 +162,15 @@ func (e *InvalidUnmarshalError) Error() string { return "json: Unmarshal(nil)" } - if e.Type.Kind() != reflect.Ptr { + if e.Type.Kind() != reflect.Pointer { return "json: Unmarshal(non-pointer " + e.Type.String() + ")" } return "json: Unmarshal(nil " + e.Type.String() + ")" } -func (d *decodeState) unmarshal(v interface{}) error { +func (d *decodeState) unmarshal(v any) error { rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { + if rv.Kind() != reflect.Pointer || rv.IsNil() { return &InvalidUnmarshalError{reflect.TypeOf(v)} } @@ -398,7 +399,7 @@ type unquotedValue struct{} // quoted string literal or literal null into an interface value. // If it finds anything other than a quoted string literal or null, // valueQuoted returns unquotedValue{}. -func (d *decodeState) valueQuoted() interface{} { +func (d *decodeState) valueQuoted() any { switch d.opcode { default: panic(phasePanicMsg) @@ -440,7 +441,7 @@ func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnm // If v is a named type and is addressable, // start with its address, so that if the type has pointer methods, // we find them. - if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() { haveAddr = true v = v.Addr() } @@ -449,14 +450,14 @@ func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnm // usefully addressable. if v.Kind() == reflect.Interface && !v.IsNil() { e := v.Elem() - if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + if e.Kind() == reflect.Pointer && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Pointer) { haveAddr = false v = e continue } } - if v.Kind() != reflect.Ptr { + if v.Kind() != reflect.Pointer { break } @@ -641,7 +642,7 @@ func (d *decodeState) object(v reflect.Value) error { reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: default: - if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { + if !reflect.PointerTo(t.Key()).Implements(textUnmarshalerType) { d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) d.skip() return nil @@ -717,7 +718,7 @@ func (d *decodeState) object(v reflect.Value) error { subv = v destring = f.quoted for _, i := range f.index { - if subv.Kind() == reflect.Ptr { + if subv.Kind() == reflect.Pointer { if subv.IsNil() { // If a struct embeds a pointer to an unexported type, // it is not possible to set a newly allocated value @@ -782,7 +783,7 @@ func (d *decodeState) object(v reflect.Value) error { kt := t.Key() var kv reflect.Value switch { - case reflect.PtrTo(kt).Implements(textUnmarshalerType): + case reflect.PointerTo(kt).Implements(textUnmarshalerType): kv = reflect.New(kt) if err := d.literalStore(item, kv, true); err != nil { return err @@ -840,7 +841,7 @@ func (d *decodeState) object(v reflect.Value) error { // convertNumber converts the number literal s to a float64 or a Number // depending on the setting of d.useNumber. -func (d *decodeState) convertNumber(s string) (interface{}, error) { +func (d *decodeState) convertNumber(s string) (any, error) { if d.useNumber { return Number(s), nil } @@ -907,7 +908,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool break } switch v.Kind() { - case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + case reflect.Interface, reflect.Pointer, reflect.Map, reflect.Slice: v.Set(reflect.Zero(v.Type())) // otherwise, ignore null for primitives/string } @@ -1037,7 +1038,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool // but they avoid the weight of reflection in this common case. // valueInterface is like value but returns interface{} -func (d *decodeState) valueInterface() (val interface{}) { +func (d *decodeState) valueInterface() (val any) { switch d.opcode { default: panic(phasePanicMsg) @@ -1054,8 +1055,8 @@ func (d *decodeState) valueInterface() (val interface{}) { } // arrayInterface is like array but returns []interface{}. -func (d *decodeState) arrayInterface() []interface{} { - var v = make([]interface{}, 0) +func (d *decodeState) arrayInterface() []any { + var v = make([]any, 0) for { // Look ahead for ] - can only happen on first iteration. d.scanWhile(scanSkipSpace) @@ -1080,8 +1081,8 @@ func (d *decodeState) arrayInterface() []interface{} { } // objectInterface is like object but returns map[string]interface{}. -func (d *decodeState) objectInterface() map[string]interface{} { - m := make(map[string]interface{}) +func (d *decodeState) objectInterface() map[string]any { + m := make(map[string]any) for { // Read opening " of string key or closing }. d.scanWhile(scanSkipSpace) @@ -1131,7 +1132,7 @@ func (d *decodeState) objectInterface() map[string]interface{} { // literalInterface consumes and returns a literal from d.data[d.off-1:] and // it reads the following byte ahead. The first byte of the literal has been // read already (that's how the caller knows it's a literal). -func (d *decodeState) literalInterface() interface{} { +func (d *decodeState) literalInterface() any { // All bytes inside literal return scanContinue op code. start := d.readIndex() d.rescanLiteral() @@ -1307,4 +1308,4 @@ func unquoteBytes(s []byte) (t []byte, ok bool) { } } return b[0:w], true -} +} \ No newline at end of file diff --git a/decode_test.go b/decode_test.go index 219e845c7b..d4c3fc2eca 100644 --- a/decode_test.go +++ b/decode_test.go @@ -31,7 +31,7 @@ type U struct { } type V struct { - F1 interface{} + F1 any F2 int32 F3 Number F4 *VOuter @@ -62,18 +62,18 @@ func (*SS) UnmarshalJSON(data []byte) error { // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and // without UseNumber -var ifaceNumAsFloat64 = map[string]interface{}{ +var ifaceNumAsFloat64 = map[string]any{ "k1": float64(1), "k2": "s", - "k3": []interface{}{float64(1), float64(2.0), float64(3e-3)}, - "k4": map[string]interface{}{"kk1": "s", "kk2": float64(2)}, + "k3": []any{float64(1), float64(2.0), float64(3e-3)}, + "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, } -var ifaceNumAsNumber = map[string]interface{}{ +var ifaceNumAsNumber = map[string]any{ "k1": Number("1"), "k2": "s", - "k3": []interface{}{Number("1"), Number("2.0"), Number("3e-3")}, - "k4": map[string]interface{}{"kk1": "s", "kk2": Number("2")}, + "k3": []any{Number("1"), Number("2.0"), Number("3e-3")}, + "k4": map[string]any{"kk1": "s", "kk2": Number("2")}, } type tx struct { @@ -262,9 +262,9 @@ type Ambig struct { } type XYZ struct { - X interface{} - Y interface{} - Z interface{} + X any + Y any + Z any } type unexportedWithMethods struct{} @@ -389,8 +389,8 @@ type mapStringToStringData struct { type unmarshalTest struct { in string - ptr interface{} // new(type) - out interface{} + ptr any // new(type) + out any err error useNumber bool golden bool @@ -414,13 +414,13 @@ var unmarshalTests = []unmarshalTest{ {in: `-5`, ptr: new(int16), out: int16(-5)}, {in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, {in: `2`, ptr: new(Number), out: Number("2")}, - {in: `2`, ptr: new(interface{}), out: float64(2.0)}, - {in: `2`, ptr: new(interface{}), out: Number("2"), useNumber: true}, + {in: `2`, ptr: new(any), out: float64(2.0)}, + {in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, {in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, {in: `"http:\/\/"`, ptr: new(string), out: "http://"}, {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, - {in: "null", ptr: new(interface{}), out: nil}, + {in: "null", ptr: new(any), out: nil}, {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(""), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, @@ -428,8 +428,8 @@ var unmarshalTests = []unmarshalTest{ {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(SS("")), 0, "W", "S"}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsNumber, useNumber: true}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, // raw values with whitespace {in: "\n true ", ptr: new(bool), out: true}, @@ -472,10 +472,10 @@ var unmarshalTests = []unmarshalTest{ {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, // empty array to interface test - {in: `[]`, ptr: new([]interface{}), out: []interface{}{}}, - {in: `null`, ptr: new([]interface{}), out: []interface{}(nil)}, - {in: `{"T":[]}`, ptr: new(map[string]interface{}), out: map[string]interface{}{"T": []interface{}{}}}, - {in: `{"T":null}`, ptr: new(map[string]interface{}), out: map[string]interface{}{"T": interface{}(nil)}}, + {in: `[]`, ptr: new([]any), out: []any{}}, + {in: `null`, ptr: new([]any), out: []any(nil)}, + {in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, + {in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, // composite tests {in: allValueIndent, ptr: new(All), out: allValue}, @@ -1103,7 +1103,7 @@ func TestUnmarshal(t *testing.T) { } typ := reflect.TypeOf(tt.ptr) - if typ.Kind() != reflect.Ptr { + if typ.Kind() != reflect.Pointer { t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr) continue } @@ -1176,7 +1176,7 @@ func TestUnmarshal(t *testing.T) { func TestUnmarshalMarshal(t *testing.T) { initBig() - var v interface{} + var v any if err := Unmarshal(jsonBig, &v); err != nil { t.Fatalf("Unmarshal: %v", err) } @@ -1248,7 +1248,7 @@ type Xint struct { func TestUnmarshalInterface(t *testing.T) { var xint Xint - var i interface{} = &xint + var i any = &xint if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { t.Fatalf("Unmarshal: %v", err) } @@ -1382,8 +1382,8 @@ type All struct { PSmall *Small PPSmall **Small - Interface interface{} - PInterface *interface{} + Interface any + PInterface *any unexported int } @@ -1717,9 +1717,9 @@ func intpp(x *int) **int { } var interfaceSetTests = []struct { - pre interface{} + pre any json string - post interface{} + post any }{ {"foo", `"bar"`, "bar"}, {"foo", `2`, 2.0}, @@ -1738,7 +1738,7 @@ var interfaceSetTests = []struct { func TestInterfaceSet(t *testing.T) { for _, tt := range interfaceSetTests { - b := struct{ X interface{} }{tt.pre} + b := struct{ X any }{tt.pre} blob := `{"X":` + tt.json + `}` if err := Unmarshal([]byte(blob), &b); err != nil { t.Errorf("Unmarshal %#q: %v", blob, err) @@ -1768,7 +1768,7 @@ type NullTest struct { PBool *bool Map map[string]string Slice []string - Interface interface{} + Interface any PRaw *RawMessage PTime *time.Time @@ -1989,7 +1989,7 @@ func TestSliceOfCustomByte(t *testing.T) { } var decodeTypeErrorTests = []struct { - dest interface{} + dest any src string }{ {new(string), `{"user": "name"}`}, // issue 4628. @@ -2022,7 +2022,7 @@ var unmarshalSyntaxTests = []string{ } func TestUnmarshalSyntax(t *testing.T) { - var x interface{} + var x any for _, src := range unmarshalSyntaxTests { err := Unmarshal([]byte(src), &x) if _, ok := err.(*SyntaxError); !ok { @@ -2035,8 +2035,8 @@ func TestUnmarshalSyntax(t *testing.T) { // Issue 4660 type unexportedFields struct { Name string - m map[string]interface{} `json:"-"` - m2 map[string]interface{} `json:"abcd"` + m map[string]any `json:"-"` + m2 map[string]any `json:"abcd"` s []int `json:"-"` } @@ -2087,7 +2087,7 @@ func TestUnmarshalJSONLiteralError(t *testing.T) { // Issue 3717 func TestSkipArrayObjects(t *testing.T) { json := `[{}]` - var dest [0]interface{} + var dest [0]any err := Unmarshal([]byte(json), &dest) if err != nil { @@ -2102,8 +2102,8 @@ func TestPrefilled(t *testing.T) { // Values here change, cannot reuse table across runs. var prefillTests = []struct { in string - ptr interface{} - out interface{} + ptr any + out any }{ { in: `{"X": 1, "Y": 2}`, @@ -2112,8 +2112,8 @@ func TestPrefilled(t *testing.T) { }, { in: `{"X": 1, "Y": 2}`, - ptr: &map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5}, - out: &map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5}, + ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, + out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, }, { in: `[2]`, @@ -2150,7 +2150,7 @@ func TestPrefilled(t *testing.T) { } var invalidUnmarshalTests = []struct { - v interface{} + v any want string }{ {nil, "json: Unmarshal(nil)"}, @@ -2173,7 +2173,7 @@ func TestInvalidUnmarshal(t *testing.T) { } var invalidUnmarshalTextTests = []struct { - v interface{} + v any want string }{ {nil, "json: Unmarshal(nil)"}, @@ -2205,7 +2205,7 @@ func TestInvalidStringOption(t *testing.T) { M map[string]string `json:",string"` S []string `json:",string"` A [1]string `json:",string"` - I interface{} `json:",string"` + I any `json:",string"` P *int `json:",string"` }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} @@ -2276,8 +2276,8 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { tests := []struct { in string - ptr interface{} - out interface{} + ptr any + out any err error }{{ // Error since we cannot set S1.embed1, but still able to set S1.R. @@ -2375,7 +2375,7 @@ func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { dec := NewDecoder(strings.NewReader(tt.in)) var err error for { - var v interface{} + var v any if err = dec.Decode(&v); err != nil { break } @@ -2403,7 +2403,7 @@ func TestUnmarshalPanic(t *testing.T) { // The decoder used to hang if decoding into an interface pointing to its own address. // See golang.org/issues/31740. func TestUnmarshalRecursivePointer(t *testing.T) { - var v interface{} + var v any v = &v data := []byte(`{"a": "b"}`) @@ -2517,36 +2517,36 @@ func TestUnmarshalMaxDepth(t *testing.T) { targets := []struct { name string - newValue func() interface{} + newValue func() any }{ { name: "unstructured", - newValue: func() interface{} { - var v interface{} + newValue: func() any { + var v any return &v }, }, { name: "typed named field", - newValue: func() interface{} { + newValue: func() any { v := struct { - A interface{} `json:"a"` + A any `json:"a"` }{} return &v }, }, { name: "typed missing field", - newValue: func() interface{} { + newValue: func() any { v := struct { - B interface{} `json:"b"` + B any `json:"b"` }{} return &v }, }, { name: "custom unmarshaler", - newValue: func() interface{} { + newValue: func() any { v := unmarshaler{} return &v }, @@ -2571,4 +2571,4 @@ func TestUnmarshalMaxDepth(t *testing.T) { }) } } -} +} \ No newline at end of file diff --git a/encode.go b/encode.go index 06b2f754c2..18f752efe0 100644 --- a/encode.go +++ b/encode.go @@ -77,31 +77,31 @@ import ( // // Examples of struct field tags and their meanings: // -// // Field appears in JSON as key "myName". -// Field int `json:"myName"` +// // Field appears in JSON as key "myName". +// Field int `json:"myName"` // -// // Field appears in JSON as key "myName" and -// // the field is omitted from the object if its value is empty, -// // as defined above. -// Field int `json:"myName,omitempty"` +// // Field appears in JSON as key "myName" and +// // the field is omitted from the object if its value is empty, +// // as defined above. +// Field int `json:"myName,omitempty"` // -// // Field appears in JSON as key "Field" (the default), but -// // the field is skipped if empty. -// // Note the leading comma. -// Field int `json:",omitempty"` +// // Field appears in JSON as key "Field" (the default), but +// // the field is skipped if empty. +// // Note the leading comma. +// Field int `json:",omitempty"` // -// // Field is ignored by this package. -// Field int `json:"-"` +// // Field is ignored by this package. +// Field int `json:"-"` // -// // Field appears in JSON as key "-". -// Field int `json:"-,"` +// // Field appears in JSON as key "-". +// Field int `json:"-,"` // // The "string" option signals that a field is stored as JSON inside a // JSON-encoded string. It applies only to fields of string, floating point, // integer, or boolean types. This extra level of encoding is sometimes used // when communicating with JavaScript programs: // -// Int64String int64 `json:",string"` +// Int64String int64 `json:",string"` // // The key name will be used if it's a non-empty string consisting of // only Unicode letters, digits, and ASCII punctuation except quotation @@ -154,9 +154,9 @@ import ( // JSON cannot represent cyclic data structures and Marshal does not // handle them. Passing cyclic structures to Marshal will result in // an error. -// -func Marshal(v interface{}) ([]byte, error) { +func Marshal(v any) ([]byte, error) { e := newEncodeState() + defer encodeStatePool.Put(e) err := e.marshal(v, encOpts{escapeHTML: true}) if err != nil { @@ -164,15 +164,13 @@ func Marshal(v interface{}) ([]byte, error) { } buf := append([]byte(nil), e.Bytes()...) - encodeStatePool.Put(e) - return buf, nil } // MarshalIndent is like Marshal but applies Indent to format the output. // Each JSON element in the output will begin on a new line beginning with prefix // followed by one or more copies of indent according to the indentation nesting. -func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { +func MarshalIndent(v any, prefix, indent string) ([]byte, error) { b, err := Marshal(v) if err != nil { return nil, err @@ -294,7 +292,7 @@ type encodeState struct { // startDetectingCyclesAfter, so that we skip the work if we're within a // reasonable amount of nested pointers deep. ptrLevel uint - ptrSeen map[interface{}]struct{} + ptrSeen map[any]struct{} } const startDetectingCyclesAfter = 1000 @@ -311,7 +309,7 @@ func newEncodeState() *encodeState { e.ptrLevel = 0 return e } - return &encodeState{ptrSeen: make(map[interface{}]struct{})} + return &encodeState{ptrSeen: make(map[any]struct{})} } // jsonError is an error wrapper type for internal use only. @@ -319,7 +317,7 @@ func newEncodeState() *encodeState { // can distinguish intentional panics from this package. type jsonError struct{ error } -func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) { +func (e *encodeState) marshal(v any, opts encOpts) (err error) { defer func() { if r := recover(); r != nil { if je, ok := r.(jsonError); ok { @@ -350,7 +348,7 @@ func isEmptyValue(v reflect.Value) bool { return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 - case reflect.Interface, reflect.Ptr: + case reflect.Interface, reflect.Pointer: return v.IsNil() case reflect.Struct: vt := v.Type() @@ -430,13 +428,13 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { // Marshaler with a value receiver, then we're better off taking // the address of the value - otherwise we end up with an // allocation as we cast the value to an interface. - if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(marshalerType) { + if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) { return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) } if t.Implements(marshalerType) { return marshalerEncoder } - if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(textMarshalerType) { + if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) { return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false)) } if t.Implements(textMarshalerType) { @@ -466,7 +464,7 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { return newSliceEncoder(t) case reflect.Array: return newArrayEncoder(t) - case reflect.Ptr: + case reflect.Pointer: return newPtrEncoder(t) default: return unsupportedTypeEncoder @@ -478,7 +476,7 @@ func invalidValueEncoder(e *encodeState, v reflect.Value, _ encOpts) { } func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { - if v.Kind() == reflect.Ptr && v.IsNil() { + if v.Kind() == reflect.Pointer && v.IsNil() { e.WriteString("null") return } @@ -515,7 +513,7 @@ func addrMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { } func textMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { - if v.Kind() == reflect.Ptr && v.IsNil() { + if v.Kind() == reflect.Pointer && v.IsNil() { e.WriteString("null") return } @@ -749,7 +747,7 @@ FieldLoop: // Find the nested struct field by following f.index. fv := v for _, i := range f.index { - if fv.Kind() == reflect.Ptr { + if fv.Kind() == reflect.Pointer { if fv.IsNil() { continue FieldLoop } @@ -795,7 +793,7 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter { // We're a large number of nested ptrEncoder.encode calls deep; // start checking if we've run into a pointer cycle. - ptr := v.Pointer() + ptr := v.UnsafePointer() if _, ok := e.ptrSeen[ptr]; ok { e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())}) } @@ -888,9 +886,9 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { // Here we use a struct to memorize the pointer to the first element of the slice // and its length. ptr := struct { - ptr uintptr + ptr interface{} // always an unsafe.Pointer, but avoids a dependency on package unsafe len int - }{v.Pointer(), v.Len()} + }{v.UnsafePointer(), v.Len()} if _, ok := e.ptrSeen[ptr]; ok { e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())}) } @@ -904,7 +902,7 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { func newSliceEncoder(t reflect.Type) encoderFunc { // Byte slices get special treatment; arrays don't. if t.Elem().Kind() == reflect.Uint8 { - p := reflect.PtrTo(t.Elem()) + p := reflect.PointerTo(t.Elem()) if !p.Implements(marshalerType) && !p.Implements(textMarshalerType) { return encodeByteSlice } @@ -1000,7 +998,7 @@ func isValidTag(s string) bool { func typeByIndex(t reflect.Type, index []int) reflect.Type { for _, i := range index { - if t.Kind() == reflect.Ptr { + if t.Kind() == reflect.Pointer { t = t.Elem() } t = t.Field(i).Type @@ -1020,7 +1018,7 @@ func (w *reflectWithString) resolve() error { return nil } if tm, ok := w.k.Interface().(encoding.TextMarshaler); ok { - if w.k.Kind() == reflect.Ptr && w.k.IsNil() { + if w.k.Kind() == reflect.Pointer && w.k.IsNil() { return nil } buf, err := tm.MarshalText() @@ -1254,7 +1252,7 @@ func typeFields(t reflect.Type) structFields { sf := f.typ.Field(i) if sf.Anonymous { t := sf.Type - if t.Kind() == reflect.Ptr { + if t.Kind() == reflect.Pointer { t = t.Elem() } if !sf.IsExported() && t.Kind() != reflect.Struct { @@ -1280,7 +1278,7 @@ func typeFields(t reflect.Type) structFields { index[len(f.index)] = i ft := sf.Type - if ft.Name() == "" && ft.Kind() == reflect.Ptr { + if ft.Name() == "" && ft.Kind() == reflect.Pointer { // Follow pointer. ft = ft.Elem() } @@ -1427,4 +1425,4 @@ func cachedTypeFields(t reflect.Type) structFields { } f, _ := fieldCache.LoadOrStore(t, typeFields(t)) return f.(structFields) -} +} \ No newline at end of file diff --git a/encode_test.go b/encode_test.go index 6636bd00f6..f9fe76933a 100644 --- a/encode_test.go +++ b/encode_test.go @@ -12,6 +12,7 @@ import ( "math" "reflect" "regexp" + "runtime/debug" "strconv" "testing" "unicode" @@ -28,8 +29,8 @@ type Optionals struct { Slr []string `json:"slr,random"` Slo []string `json:"slo,omitempty"` - Mr map[string]interface{} `json:"mr"` - Mo map[string]interface{} `json:",omitempty"` + Mr map[string]any `json:"mr"` + Mo map[string]any `json:",omitempty"` Fr float64 `json:"fr"` Fo float64 `json:"fo,omitempty"` @@ -52,14 +53,15 @@ var optionalsExpected = `{ "fr": 0, "br": false, "ur": 0, - "str": {} + "str": {}, + "sto": {} }` func TestOmitEmpty(t *testing.T) { var o Optionals o.Sw = "something" - o.Mr = map[string]interface{}{} - o.Mo = map[string]interface{}{} + o.Mr = map[string]any{} + o.Mo = map[string]any{} got, err := MarshalIndent(&o, "", " ") if err != nil { @@ -179,16 +181,16 @@ type PointerCycle struct { var pointerCycle = &PointerCycle{} type PointerCycleIndirect struct { - Ptrs []interface{} + Ptrs []any } type RecursiveSlice []RecursiveSlice var ( pointerCycleIndirect = &PointerCycleIndirect{} - mapCycle = make(map[string]interface{}) - sliceCycle = []interface{}{nil} - sliceNoCycle = []interface{}{nil, nil} + mapCycle = make(map[string]any) + sliceCycle = []any{nil} + sliceNoCycle = []any{nil, nil} recursiveSliceCycle = []RecursiveSlice{nil} ) @@ -198,13 +200,13 @@ func init() { samePointerNoCycle.Ptr2 = ptr pointerCycle.Ptr = pointerCycle - pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect} + pointerCycleIndirect.Ptrs = []any{pointerCycleIndirect} mapCycle["x"] = mapCycle sliceCycle[0] = sliceCycle sliceNoCycle[1] = sliceNoCycle[:1] for i := startDetectingCyclesAfter; i > 0; i-- { - sliceNoCycle = []interface{}{sliceNoCycle} + sliceNoCycle = []any{sliceNoCycle} } recursiveSliceCycle[0] = recursiveSliceCycle } @@ -221,7 +223,7 @@ func TestSliceNoCycle(t *testing.T) { } } -var unsupportedValues = []interface{}{ +var unsupportedValues = []any{ math.NaN(), math.Inf(-1), math.Inf(1), @@ -366,15 +368,15 @@ func TestMarshalerEscaping(t *testing.T) { func TestAnonymousFields(t *testing.T) { tests := []struct { - label string // Test name - makeInput func() interface{} // Function to create input value - want string // Expected JSON output + label string // Test name + makeInput func() any // Function to create input value + want string // Expected JSON output }{{ // Both S1 and S2 have a field named X. From the perspective of S, // it is ambiguous which one X refers to. // This should not serialize either field. label: "AmbiguousField", - makeInput: func() interface{} { + makeInput: func() any { type ( S1 struct{ x, X int } S2 struct{ x, X int } @@ -390,7 +392,7 @@ func TestAnonymousFields(t *testing.T) { label: "DominantField", // Both S1 and S2 have a field named X, but since S has an X field as // well, it takes precedence over S1.X and S2.X. - makeInput: func() interface{} { + makeInput: func() any { type ( S1 struct{ x, X int } S2 struct{ x, X int } @@ -406,7 +408,7 @@ func TestAnonymousFields(t *testing.T) { }, { // Unexported embedded field of non-struct type should not be serialized. label: "UnexportedEmbeddedInt", - makeInput: func() interface{} { + makeInput: func() any { type ( myInt int S struct{ myInt } @@ -417,7 +419,7 @@ func TestAnonymousFields(t *testing.T) { }, { // Exported embedded field of non-struct type should be serialized. label: "ExportedEmbeddedInt", - makeInput: func() interface{} { + makeInput: func() any { type ( MyInt int S struct{ MyInt } @@ -429,7 +431,7 @@ func TestAnonymousFields(t *testing.T) { // Unexported embedded field of pointer to non-struct type // should not be serialized. label: "UnexportedEmbeddedIntPointer", - makeInput: func() interface{} { + makeInput: func() any { type ( myInt int S struct{ *myInt } @@ -443,7 +445,7 @@ func TestAnonymousFields(t *testing.T) { // Exported embedded field of pointer to non-struct type // should be serialized. label: "ExportedEmbeddedIntPointer", - makeInput: func() interface{} { + makeInput: func() any { type ( MyInt int S struct{ *MyInt } @@ -458,7 +460,7 @@ func TestAnonymousFields(t *testing.T) { // exported fields be serialized regardless of whether the struct types // themselves are exported. label: "EmbeddedStruct", - makeInput: func() interface{} { + makeInput: func() any { type ( s1 struct{ x, X int } S2 struct{ y, Y int } @@ -475,7 +477,7 @@ func TestAnonymousFields(t *testing.T) { // exported fields be serialized regardless of whether the struct types // themselves are exported. label: "EmbeddedStructPointer", - makeInput: func() interface{} { + makeInput: func() any { type ( s1 struct{ x, X int } S2 struct{ y, Y int } @@ -491,7 +493,7 @@ func TestAnonymousFields(t *testing.T) { // Exported fields on embedded unexported structs at multiple levels // of nesting should still be serialized. label: "NestedStructAndInts", - makeInput: func() interface{} { + makeInput: func() any { type ( MyInt1 int MyInt2 int @@ -518,7 +520,7 @@ func TestAnonymousFields(t *testing.T) { // the embedded fields behind it. Not properly doing so may // result in the wrong output or reflect panics. label: "EmbeddedFieldBehindNilPointer", - makeInput: func() interface{} { + makeInput: func() any { type ( S2 struct{ Field string } S struct{ *S2 } @@ -588,22 +590,22 @@ func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { // See golang.org/issue/16042 and golang.org/issue/34235. func TestNilMarshal(t *testing.T) { testCases := []struct { - v interface{} + v any want string }{ {v: nil, want: `null`}, {v: new(float64), want: `0`}, - {v: []interface{}(nil), want: `null`}, + {v: []any(nil), want: `null`}, {v: []string(nil), want: `null`}, {v: map[string]string(nil), want: `null`}, {v: []byte(nil), want: `null`}, {v: struct{ M string }{"gopher"}, want: `{"M":"gopher"}`}, {v: struct{ M Marshaler }{}, want: `{"M":null}`}, {v: struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M interface{} }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, + {v: struct{ M any }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, {v: struct{ M encoding.TextMarshaler }{}, want: `{"M":null}`}, {v: struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M interface{} }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, + {v: struct{ M any }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, } for _, tt := range testCases { @@ -759,6 +761,41 @@ func TestIssue10281(t *testing.T) { } } +func TestMarshalErrorAndReuseEncodeState(t *testing.T) { + // Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test. + percent := debug.SetGCPercent(-1) + defer debug.SetGCPercent(percent) + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + if b, err := Marshal(dummy); err == nil { + t.Errorf("Marshal(dummy) = %#q; want error", b) + } + + type Data struct { + A string + I int + } + data := Data{A: "a", I: 1} + b, err := Marshal(data) + if err != nil { + t.Errorf("Marshal(%v) = %v", data, err) + } + + var data2 Data + if err := Unmarshal(b, &data2); err != nil { + t.Errorf("Unmarshal(%v) = %v", data2, err) + } + if data2 != data { + t.Errorf("expect: %v, but get: %v", data, data2) + } +} + func TestHTMLEscape(t *testing.T) { var b, want bytes.Buffer m := `{"M":"foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `"}` @@ -863,7 +900,7 @@ type textint int func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } -func tenc(format string, a ...interface{}) ([]byte, error) { +func tenc(format string, a ...any) ([]byte, error) { var buf bytes.Buffer fmt.Fprintf(&buf, format, a...) return buf.Bytes(), nil @@ -876,7 +913,7 @@ func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) } // Issue 13783 func TestEncodeBytekind(t *testing.T) { testdata := []struct { - data interface{} + data any want string }{ {byte(7), "7"}, @@ -965,7 +1002,7 @@ func TestMarshalFloat(t *testing.T) { t.Parallel() nfail := 0 test := func(f float64, bits int) { - vf := interface{}(f) + vf := any(f) if bits == 32 { f = float64(float32(f)) // round vf = float32(f) @@ -1061,25 +1098,25 @@ func TestMarshalRawMessageValue(t *testing.T) { ) tests := []struct { - in interface{} + in any want string ok bool }{ // Test with nil RawMessage. {rawNil, "null", true}, {&rawNil, "null", true}, - {[]interface{}{rawNil}, "[null]", true}, - {&[]interface{}{rawNil}, "[null]", true}, - {[]interface{}{&rawNil}, "[null]", true}, - {&[]interface{}{&rawNil}, "[null]", true}, + {[]any{rawNil}, "[null]", true}, + {&[]any{rawNil}, "[null]", true}, + {[]any{&rawNil}, "[null]", true}, + {&[]any{&rawNil}, "[null]", true}, {struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, {&struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, {struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, {&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {map[string]interface{}{"M": rawNil}, `{"M":null}`, true}, - {&map[string]interface{}{"M": rawNil}, `{"M":null}`, true}, - {map[string]interface{}{"M": &rawNil}, `{"M":null}`, true}, - {&map[string]interface{}{"M": &rawNil}, `{"M":null}`, true}, + {map[string]any{"M": rawNil}, `{"M":null}`, true}, + {&map[string]any{"M": rawNil}, `{"M":null}`, true}, + {map[string]any{"M": &rawNil}, `{"M":null}`, true}, + {&map[string]any{"M": &rawNil}, `{"M":null}`, true}, {T1{rawNil}, "{}", true}, {T2{&rawNil}, `{"M":null}`, true}, {&T1{rawNil}, "{}", true}, @@ -1088,18 +1125,18 @@ func TestMarshalRawMessageValue(t *testing.T) { // Test with empty, but non-nil, RawMessage. {rawEmpty, "", false}, {&rawEmpty, "", false}, - {[]interface{}{rawEmpty}, "", false}, - {&[]interface{}{rawEmpty}, "", false}, - {[]interface{}{&rawEmpty}, "", false}, - {&[]interface{}{&rawEmpty}, "", false}, + {[]any{rawEmpty}, "", false}, + {&[]any{rawEmpty}, "", false}, + {[]any{&rawEmpty}, "", false}, + {&[]any{&rawEmpty}, "", false}, {struct{ X RawMessage }{rawEmpty}, "", false}, {&struct{ X RawMessage }{rawEmpty}, "", false}, {struct{ X *RawMessage }{&rawEmpty}, "", false}, {&struct{ X *RawMessage }{&rawEmpty}, "", false}, - {map[string]interface{}{"nil": rawEmpty}, "", false}, - {&map[string]interface{}{"nil": rawEmpty}, "", false}, - {map[string]interface{}{"nil": &rawEmpty}, "", false}, - {&map[string]interface{}{"nil": &rawEmpty}, "", false}, + {map[string]any{"nil": rawEmpty}, "", false}, + {&map[string]any{"nil": rawEmpty}, "", false}, + {map[string]any{"nil": &rawEmpty}, "", false}, + {&map[string]any{"nil": &rawEmpty}, "", false}, {T1{rawEmpty}, "{}", true}, {T2{&rawEmpty}, "", false}, {&T1{rawEmpty}, "{}", true}, @@ -1112,18 +1149,18 @@ func TestMarshalRawMessageValue(t *testing.T) { // See https://golang.org/issues/14493#issuecomment-255857318 {rawText, `"foo"`, true}, // Issue6458 {&rawText, `"foo"`, true}, - {[]interface{}{rawText}, `["foo"]`, true}, // Issue6458 - {&[]interface{}{rawText}, `["foo"]`, true}, // Issue6458 - {[]interface{}{&rawText}, `["foo"]`, true}, - {&[]interface{}{&rawText}, `["foo"]`, true}, + {[]any{rawText}, `["foo"]`, true}, // Issue6458 + {&[]any{rawText}, `["foo"]`, true}, // Issue6458 + {[]any{&rawText}, `["foo"]`, true}, + {&[]any{&rawText}, `["foo"]`, true}, {struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 {&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, {struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, {&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true}, - {&map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true}, + {map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {&map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, + {&map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 {T2{&rawText}, `{"M":"foo"}`, true}, {&T1{rawText}, `{"M":"foo"}`, true}, @@ -1199,4 +1236,4 @@ func TestMarshalerError(t *testing.T) { t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) } } -} +} \ No newline at end of file diff --git a/example_marshaling_test.go b/example_marshaling_test.go index 7f15c742b8..c3c57e1531 100644 --- a/example_marshaling_test.go +++ b/example_marshaling_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Go Authors. All rights reserved. +// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -11,63 +11,57 @@ import ( "strings" ) -type Animal int +type Size int const ( - Unknown Animal = iota - Gopher - Zebra + Unrecognized Size = iota + Small + Large ) -func (a *Animal) UnmarshalJSON(b []byte) error { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - switch strings.ToLower(s) { +func (s *Size) UnmarshalText(text []byte) error { + switch strings.ToLower(string(text)) { default: - *a = Unknown - case "gopher": - *a = Gopher - case "zebra": - *a = Zebra + *s = Unrecognized + case "small": + *s = Small + case "large": + *s = Large } - return nil } -func (a Animal) MarshalJSON() ([]byte, error) { - var s string - switch a { +func (s Size) MarshalText() ([]byte, error) { + var name string + switch s { default: - s = "unknown" - case Gopher: - s = "gopher" - case Zebra: - s = "zebra" + name = "unrecognized" + case Small: + name = "small" + case Large: + name = "large" } - - return json.Marshal(s) + return []byte(name), nil } -func Example_customMarshalJSON() { - blob := `["gopher","armadillo","zebra","unknown","gopher","bee","gopher","zebra"]` - var zoo []Animal - if err := json.Unmarshal([]byte(blob), &zoo); err != nil { +func Example_textMarshalJSON() { + blob := `["small","regular","large","unrecognized","small","normal","small","large"]` + var inventory []Size + if err := json.Unmarshal([]byte(blob), &inventory); err != nil { log.Fatal(err) } - census := make(map[Animal]int) - for _, animal := range zoo { - census[animal] += 1 + counts := make(map[Size]int) + for _, size := range inventory { + counts[size] += 1 } - fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n", - census[Gopher], census[Zebra], census[Unknown]) + fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n", + counts[Small], counts[Large], counts[Unrecognized]) // Output: - // Zoo Census: - // * Gophers: 3 - // * Zebras: 2 - // * Unknown: 3 -} + // Inventory Counts: + // * Small: 3 + // * Large: 2 + // * Unrecognized: 3 +} \ No newline at end of file diff --git a/example_test.go b/example_test.go index fbecf1b593..6200da5fea 100644 --- a/example_test.go +++ b/example_test.go @@ -200,7 +200,7 @@ func ExampleRawMessage_unmarshal() { } for _, c := range colors { - var dst interface{} + var dst any switch c.Space { case "RGB": dst = new(RGB) @@ -307,4 +307,4 @@ func ExampleHTMLEscape() { out.WriteTo(os.Stdout) // Output: //{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"} -} +} \ No newline at end of file diff --git a/example_text_marshaling_test.go b/example_text_marshaling_test.go index 04c7813b26..44fdf5868b 100644 --- a/example_text_marshaling_test.go +++ b/example_text_marshaling_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Go Authors. All rights reserved. +// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -11,57 +11,63 @@ import ( "strings" ) -type Size int +type Animal int const ( - Unrecognized Size = iota - Small - Large + Unknown Animal = iota + Gopher + Zebra ) -func (s *Size) UnmarshalText(text []byte) error { - switch strings.ToLower(string(text)) { +func (a *Animal) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + switch strings.ToLower(s) { default: - *s = Unrecognized - case "small": - *s = Small - case "large": - *s = Large + *a = Unknown + case "gopher": + *a = Gopher + case "zebra": + *a = Zebra } + return nil } -func (s Size) MarshalText() ([]byte, error) { - var name string - switch s { +func (a Animal) MarshalJSON() ([]byte, error) { + var s string + switch a { default: - name = "unrecognized" - case Small: - name = "small" - case Large: - name = "large" + s = "unknown" + case Gopher: + s = "gopher" + case Zebra: + s = "zebra" } - return []byte(name), nil + + return json.Marshal(s) } -func Example_textMarshalJSON() { - blob := `["small","regular","large","unrecognized","small","normal","small","large"]` - var inventory []Size - if err := json.Unmarshal([]byte(blob), &inventory); err != nil { +func Example_customMarshalJSON() { + blob := `["gopher","armadillo","zebra","unknown","gopher","bee","gopher","zebra"]` + var zoo []Animal + if err := json.Unmarshal([]byte(blob), &zoo); err != nil { log.Fatal(err) } - counts := make(map[Size]int) - for _, size := range inventory { - counts[size] += 1 + census := make(map[Animal]int) + for _, animal := range zoo { + census[animal] += 1 } - fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n", - counts[Small], counts[Large], counts[Unrecognized]) + fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n", + census[Gopher], census[Zebra], census[Unknown]) // Output: - // Inventory Counts: - // * Small: 3 - // * Large: 2 - // * Unrecognized: 3 -} + // Zoo Census: + // * Gophers: 3 + // * Zebras: 2 + // * Unknown: 3 +} \ No newline at end of file diff --git a/fold.go b/fold.go index 9e170127db..1c4a704ea9 100644 --- a/fold.go +++ b/fold.go @@ -24,8 +24,9 @@ const ( // 4) simpleLetterEqualFold, no specials, no non-letters. // // The letters S and K are special because they map to 3 runes, not just 2: -// * S maps to s and to U+017F 'ſ' Latin small letter long s -// * k maps to K and to U+212A 'K' Kelvin sign +// - S maps to s and to U+017F 'ſ' Latin small letter long s +// - k maps to K and to U+212A 'K' Kelvin sign +// // See https://play.golang.org/p/tTxjOc0OGo // // The returned function is specialized for matching against s and @@ -96,10 +97,7 @@ func equalFoldRight(s, t []byte) bool { t = t[size:] } - if len(t) > 0 { - return false - } - return true + return len(t) == 0 } // asciiEqualFold is a specialization of bytes.EqualFold for use when @@ -140,4 +138,4 @@ func simpleLetterEqualFold(s, t []byte) bool { } } return true -} +} \ No newline at end of file diff --git a/fold_test.go b/fold_test.go index 9fb94646a8..294d677bb5 100644 --- a/fold_test.go +++ b/fold_test.go @@ -52,9 +52,7 @@ func TestFold(t *testing.T) { } func TestFoldAgainstUnicode(t *testing.T) { - const bufSize = 5 - buf1 := make([]byte, 0, bufSize) - buf2 := make([]byte, 0, bufSize) + var buf1, buf2 []byte var runes []rune for i := 0x20; i <= 0x7f; i++ { runes = append(runes, rune(i)) @@ -96,12 +94,8 @@ func TestFoldAgainstUnicode(t *testing.T) { continue } for _, r2 := range runes { - buf1 := append(buf1[:0], 'x') - buf2 := append(buf2[:0], 'x') - buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)] - buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)] - buf1 = append(buf1, 'x') - buf2 = append(buf2, 'x') + buf1 = append(utf8.AppendRune(append(buf1[:0], 'x'), r), 'x') + buf2 = append(utf8.AppendRune(append(buf2[:0], 'x'), r2), 'x') want := bytes.EqualFold(buf1, buf2) if got := ff.fold(buf1, buf2); got != want { t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want) @@ -113,4 +107,4 @@ func TestFoldAgainstUnicode(t *testing.T) { func isASCIILetter(b byte) bool { return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') -} +} \ No newline at end of file diff --git a/fuzz_test.go b/fuzz_test.go new file mode 100644 index 0000000000..ace40a9453 --- /dev/null +++ b/fuzz_test.go @@ -0,0 +1,83 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import ( + "bytes" + "io" + "testing" +) + +func FuzzUnmarshalJSON(f *testing.F) { + f.Add([]byte(`{ +"object": { + "slice": [ + 1, + 2.0, + "3", + [4], + {5: {}} + ] +}, +"slice": [[]], +"string": ":)", +"int": 1e5, +"float": 3e-9" +}`)) + + f.Fuzz(func(t *testing.T, b []byte) { + for _, typ := range []func() interface{}{ + func() interface{} { return new(interface{}) }, + func() interface{} { return new(map[string]interface{}) }, + func() interface{} { return new([]interface{}) }, + } { + i := typ() + if err := Unmarshal(b, i); err != nil { + return + } + + encoded, err := Marshal(i) + if err != nil { + t.Fatalf("failed to marshal: %s", err) + } + + if err := Unmarshal(encoded, i); err != nil { + t.Fatalf("failed to roundtrip: %s", err) + } + } + }) +} + +func FuzzDecoderToken(f *testing.F) { + f.Add([]byte(`{ +"object": { + "slice": [ + 1, + 2.0, + "3", + [4], + {5: {}} + ] +}, +"slice": [[]], +"string": ":)", +"int": 1e5, +"float": 3e-9" +}`)) + + f.Fuzz(func(t *testing.T, b []byte) { + r := bytes.NewReader(b) + d := NewDecoder(r) + for { + _, err := d.Token() + if err != nil { + if err == io.EOF { + break + } + return + } + } + }) +} \ No newline at end of file diff --git a/go.mods b/go.mods new file mode 100644 index 0000000000..ce6f95d5bc --- /dev/null +++ b/go.mods @@ -0,0 +1,3 @@ +// module github.com/clarketm/json + +// go 1.19 diff --git a/indent.go b/indent.go index 2924d3b49b..6908a79650 100644 --- a/indent.go +++ b/indent.go @@ -140,4 +140,4 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { return scan.err } return nil -} +} \ No newline at end of file diff --git a/number_test.go b/number_test.go index cc6701814f..1abe36e11b 100644 --- a/number_test.go +++ b/number_test.go @@ -115,19 +115,4 @@ func TestNumberIsValid(t *testing.T) { t.Errorf("%s should be invalid but matches regexp", test) } } -} - -func BenchmarkNumberIsValid(b *testing.B) { - s := "-61657.61667E+61673" - for i := 0; i < b.N; i++ { - isValidNumber(s) - } -} - -func BenchmarkNumberIsValidRegexp(b *testing.B) { - var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) - s := "-61657.61667E+61673" - for i := 0; i < b.N; i++ { - jsonNumberRegexp.MatchString(s) - } -} +} \ No newline at end of file diff --git a/scanner.go b/scanner.go index 9dc1903e2d..58ae9aa674 100644 --- a/scanner.go +++ b/scanner.go @@ -27,6 +27,7 @@ func Valid(data []byte) bool { // checkValid verifies that data is valid JSON-encoded data. // scan is passed in for use by checkValid to avoid an allocation. +// checkValid returns nil or a SyntaxError. func checkValid(data []byte, scan *scanner) error { scan.reset() for _, c := range data { @@ -42,6 +43,7 @@ func checkValid(data []byte, scan *scanner) error { } // A SyntaxError is a description of a JSON syntax error. +// Unmarshal will return a SyntaxError if the JSON can't be parsed. type SyntaxError struct { msg string // description of error Offset int64 // error occurred after reading Offset bytes @@ -83,7 +85,7 @@ type scanner struct { } var scannerPool = sync.Pool{ - New: func() interface{} { + New: func() any { return &scanner{} }, } @@ -605,4 +607,4 @@ func quoteChar(c byte) string { // use quoted string with different quotation marks s := strconv.Quote(string(c)) return "'" + s[1:len(s)-1] + "'" -} +} \ No newline at end of file diff --git a/scanner_test.go b/scanner_test.go index 3737516a45..b8c3b9e777 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -237,7 +237,7 @@ func initBig() { jsonBig = b } -func genValue(n int) interface{} { +func genValue(n int) any { if n > 1 { switch rand.Intn(2) { case 0: @@ -270,7 +270,7 @@ func genString(stddev float64) string { return string(c) } -func genArray(n int) []interface{} { +func genArray(n int) []any { f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) if f > n { f = n @@ -278,14 +278,14 @@ func genArray(n int) []interface{} { if f < 1 { f = 1 } - x := make([]interface{}, f) + x := make([]any, f) for i := range x { x[i] = genValue(((i+1)*n)/f - (i*n)/f) } return x } -func genMap(n int) map[string]interface{} { +func genMap(n int) map[string]any { f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) if f > n { f = n @@ -293,9 +293,9 @@ func genMap(n int) map[string]interface{} { if n > 0 && f == 0 { f = 1 } - x := make(map[string]interface{}) + x := make(map[string]any) for i := 0; i < f; i++ { x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) } return x -} +} \ No newline at end of file diff --git a/stream.go b/stream.go index 81f404f426..836189da57 100644 --- a/stream.go +++ b/stream.go @@ -46,7 +46,7 @@ func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true // // See the documentation for Unmarshal for details about // the conversion of JSON into a Go value. -func (dec *Decoder) Decode(v interface{}) error { +func (dec *Decoder) Decode(v any) error { if dec.err != nil { return dec.err } @@ -198,11 +198,14 @@ func NewEncoder(w io.Writer) *Encoder { // // See the documentation for Marshal for details about the // conversion of Go values to JSON. -func (enc *Encoder) Encode(v interface{}) error { +func (enc *Encoder) Encode(v any) error { if enc.err != nil { return enc.err } + e := newEncodeState() + defer encodeStatePool.Put(e) + err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML}) if err != nil { return err @@ -231,7 +234,6 @@ func (enc *Encoder) Encode(v interface{}) error { if _, err = enc.w.Write(b); err != nil { enc.err = err } - encodeStatePool.Put(e) return err } @@ -287,8 +289,7 @@ var _ Unmarshaler = (*RawMessage)(nil) // Number, for JSON numbers // string, for JSON string literals // nil, for JSON null -// -type Token interface{} +type Token any const ( tokenTopValue = iota @@ -452,7 +453,7 @@ func (dec *Decoder) Token() (Token, error) { if !dec.tokenValueAllowed() { return dec.tokenError(c) } - var x interface{} + var x any if err := dec.Decode(&x); err != nil { return nil, err } @@ -511,4 +512,4 @@ func (dec *Decoder) peek() (byte, error) { // and the beginning of the next token. func (dec *Decoder) InputOffset() int64 { return dec.scanned + int64(dec.scanp) -} +} \ No newline at end of file diff --git a/stream_test.go b/stream_test.go index c284f2d965..bc37e651ab 100644 --- a/stream_test.go +++ b/stream_test.go @@ -12,20 +12,21 @@ import ( "net/http" "net/http/httptest" "reflect" + "runtime/debug" "strings" "testing" ) // Test values for the stream test. // One of each JSON kind. -var streamTest = []interface{}{ +var streamTest = []any{ 0.1, "hello", nil, true, false, - []interface{}{"a", "b", "c"}, - map[string]interface{}{"K": "Kelvin", "ß": "long s"}, + []any{"a", "b", "c"}, + map[string]any{"K": "Kelvin", "ß": "long s"}, 3.14, // another value to make sure something can follow map } @@ -41,7 +42,7 @@ false func TestEncoder(t *testing.T) { for i := 0; i <= len(streamTest); i++ { - var buf bytes.Buffer + var buf strings.Builder enc := NewEncoder(&buf) // Check that enc.SetIndent("", "") turns off indentation. enc.SetIndent(">", ".") @@ -59,6 +60,43 @@ func TestEncoder(t *testing.T) { } } +func TestEncoderErrorAndReuseEncodeState(t *testing.T) { + // Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test. + percent := debug.SetGCPercent(-1) + defer debug.SetGCPercent(percent) + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + var buf bytes.Buffer + enc := NewEncoder(&buf) + if err := enc.Encode(dummy); err == nil { + t.Errorf("Encode(dummy) == nil; want error") + } + + type Data struct { + A string + I int + } + data := Data{A: "a", I: 1} + if err := enc.Encode(data); err != nil { + t.Errorf("Marshal(%v) = %v", data, err) + } + + var data2 Data + if err := Unmarshal(buf.Bytes(), &data2); err != nil { + t.Errorf("Unmarshal(%v) = %v", data2, err) + } + if data2 != data { + t.Errorf("expect: %v, but get: %v", data, data2) + } +} + var streamEncodedIndent = `0.1 "hello" null @@ -77,7 +115,7 @@ false ` func TestEncoderIndent(t *testing.T) { - var buf bytes.Buffer + var buf strings.Builder enc := NewEncoder(&buf) enc.SetIndent(">", ".") for _, v := range streamTest { @@ -124,7 +162,7 @@ func TestEncoderSetEscapeHTML(t *testing.T) { for _, tt := range []struct { name string - v interface{} + v any wantEscape string want string }{ @@ -147,7 +185,7 @@ func TestEncoderSetEscapeHTML(t *testing.T) { `{"bar":"\"foobar\""}`, }, } { - var buf bytes.Buffer + var buf strings.Builder enc := NewEncoder(&buf) if err := enc.Encode(tt.v); err != nil { t.Errorf("Encode(%s): %s", tt.name, err) @@ -182,7 +220,7 @@ func TestDecoder(t *testing.T) { buf.WriteRune(c) } } - out := make([]interface{}, i) + out := make([]any, i) dec := NewDecoder(&buf) for j := range out { if err := dec.Decode(&out[j]); err != nil { @@ -297,7 +335,7 @@ func TestBlocking(t *testing.T) { for _, enc := range blockingTests { r, w := net.Pipe() go w.Write([]byte(enc)) - var val interface{} + var val any // If Decode reads beyond what w.Write writes above, // it will block, and the test will deadlock. @@ -309,97 +347,82 @@ func TestBlocking(t *testing.T) { } } -func BenchmarkEncoderEncode(b *testing.B) { - b.ReportAllocs() - type T struct { - X, Y string - } - v := &T{"foo", "bar"} - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if err := NewEncoder(io.Discard).Encode(v); err != nil { - b.Fatal(err) - } - } - }) -} - type tokenStreamCase struct { json string - expTokens []interface{} + expTokens []any } type decodeThis struct { - v interface{} + v any } var tokenStreamCases = []tokenStreamCase{ // streaming token cases - {json: `10`, expTokens: []interface{}{float64(10)}}, - {json: ` [10] `, expTokens: []interface{}{ + {json: `10`, expTokens: []any{float64(10)}}, + {json: ` [10] `, expTokens: []any{ Delim('['), float64(10), Delim(']')}}, - {json: ` [false,10,"b"] `, expTokens: []interface{}{ + {json: ` [false,10,"b"] `, expTokens: []any{ Delim('['), false, float64(10), "b", Delim(']')}}, - {json: `{ "a": 1 }`, expTokens: []interface{}{ + {json: `{ "a": 1 }`, expTokens: []any{ Delim('{'), "a", float64(1), Delim('}')}}, - {json: `{"a": 1, "b":"3"}`, expTokens: []interface{}{ + {json: `{"a": 1, "b":"3"}`, expTokens: []any{ Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, - {json: ` [{"a": 1},{"a": 2}] `, expTokens: []interface{}{ + {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ Delim('['), Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "a", float64(2), Delim('}'), Delim(']')}}, - {json: `{"obj": {"a": 1}}`, expTokens: []interface{}{ + {json: `{"obj": {"a": 1}}`, expTokens: []any{ Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), Delim('}')}}, - {json: `{"obj": [{"a": 1}]}`, expTokens: []interface{}{ + {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ Delim('{'), "obj", Delim('['), Delim('{'), "a", float64(1), Delim('}'), Delim(']'), Delim('}')}}, // streaming tokens with intermittent Decode() - {json: `{ "a": 1 }`, expTokens: []interface{}{ + {json: `{ "a": 1 }`, expTokens: []any{ Delim('{'), "a", decodeThis{float64(1)}, Delim('}')}}, - {json: ` [ { "a" : 1 } ] `, expTokens: []interface{}{ + {json: ` [ { "a" : 1 } ] `, expTokens: []any{ Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, Delim(']')}}, - {json: ` [{"a": 1},{"a": 2}] `, expTokens: []interface{}{ + {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, - decodeThis{map[string]interface{}{"a": float64(2)}}, + decodeThis{map[string]any{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(2)}}, Delim(']')}}, - {json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []interface{}{ + {json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ Delim('{'), "obj", Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, Delim(']'), Delim('}')}}, - {json: `{"obj": {"a": 1}}`, expTokens: []interface{}{ + {json: `{"obj": {"a": 1}}`, expTokens: []any{ Delim('{'), "obj", - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, Delim('}')}}, - {json: `{"obj": [{"a": 1}]}`, expTokens: []interface{}{ + {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ Delim('{'), "obj", - decodeThis{[]interface{}{ - map[string]interface{}{"a": float64(1)}, + decodeThis{[]any{ + map[string]any{"a": float64(1)}, }}, Delim('}')}}, - {json: ` [{"a": 1} {"a": 2}] `, expTokens: []interface{}{ + {json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, decodeThis{&SyntaxError{"expected comma after array element", 11}}, }}, - {json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []interface{}{ + {json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{ Delim('{'), strings.Repeat("a", 513), decodeThis{&SyntaxError{"expected colon after object key", 518}}, }}, - {json: `{ "\a" }`, expTokens: []interface{}{ + {json: `{ "\a" }`, expTokens: []any{ Delim('{'), &SyntaxError{"invalid character 'a' in string escape code", 3}, }}, - {json: ` \a`, expTokens: []interface{}{ + {json: ` \a`, expTokens: []any{ &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1}, }}, } @@ -410,7 +433,7 @@ func TestDecodeInStream(t *testing.T) { dec := NewDecoder(strings.NewReader(tcase.json)) for i, etk := range tcase.expTokens { - var tk interface{} + var tk any var err error if dt, ok := etk.(decodeThis); ok { @@ -471,4 +494,4 @@ func TestHTTPDecoding(t *testing.T) { if err != io.EOF { t.Errorf("err = %v; want io.EOF", err) } -} +} \ No newline at end of file diff --git a/tables.go b/tables.go index 10acdc18c6..cd920adda8 100644 --- a/tables.go +++ b/tables.go @@ -215,4 +215,4 @@ var htmlSafeSet = [utf8.RuneSelf]bool{ '}': true, '~': true, '\u007f': true, -} +} \ No newline at end of file diff --git a/tagkey_test.go b/tagkey_test.go index bbb4e6a28d..a9703b7251 100644 --- a/tagkey_test.go +++ b/tagkey_test.go @@ -73,7 +73,7 @@ type unicodeTag struct { } var structTagObjectKeyTests = []struct { - raw interface{} + raw any value string key string }{ @@ -101,12 +101,12 @@ func TestStructTagObjectKey(t *testing.T) { if err != nil { t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) } - var f interface{} + var f any err = Unmarshal(b, &f) if err != nil { t.Fatalf("Unmarshal(%#q) failed: %v", b, err) } - for i, v := range f.(map[string]interface{}) { + for i, v := range f.(map[string]any) { switch i { case tt.key: if s, ok := v.(string); !ok || s != tt.value { @@ -117,4 +117,4 @@ func TestStructTagObjectKey(t *testing.T) { } } } -} +} \ No newline at end of file diff --git a/tags.go b/tags.go index c38fd5102f..7fec416163 100644 --- a/tags.go +++ b/tags.go @@ -15,10 +15,8 @@ type tagOptions string // parseTag splits a struct field's json tag into its name and // comma-separated options. func parseTag(tag string) (string, tagOptions) { - if idx := strings.Index(tag, ","); idx != -1 { - return tag[:idx], tagOptions(tag[idx+1:]) - } - return tag, tagOptions("") + tag, opt, _ := strings.Cut(tag, ",") + return tag, tagOptions(opt) } // Contains reports whether a comma-separated list of options @@ -30,15 +28,11 @@ func (o tagOptions) Contains(optionName string) bool { } s := string(o) for s != "" { - var next string - i := strings.Index(s, ",") - if i >= 0 { - s, next = s[:i], s[i+1:] - } - if s == optionName { + var name string + name, s, _ = strings.Cut(s, ",") + if name == optionName { return true } - s = next } return false -} +} \ No newline at end of file diff --git a/tags_test.go b/tags_test.go index 8ba8ddd5f8..8b1e8a3a2d 100644 --- a/tags_test.go +++ b/tags_test.go @@ -25,4 +25,4 @@ func TestTagParsing(t *testing.T) { t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) } } -} +} \ No newline at end of file From d1de09c92672b09d5d1415231b6e15e2a6318298 Mon Sep 17 00:00:00 2001 From: mrredo <74524695+mrredo@users.noreply.github.com> Date: Mon, 10 Oct 2022 19:17:55 +0300 Subject: [PATCH 4/7] Delete go.mods --- go.mods | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 go.mods diff --git a/go.mods b/go.mods deleted file mode 100644 index ce6f95d5bc..0000000000 --- a/go.mods +++ /dev/null @@ -1,3 +0,0 @@ -// module github.com/clarketm/json - -// go 1.19 From 9b9d6af56166f7c36131bd016b1739acc7edeb2e Mon Sep 17 00:00:00 2001 From: mrredo <74524695+mrredo@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:43:10 +0300 Subject: [PATCH 5/7] Update fuzz.go --- fuzz.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fuzz.go b/fuzz.go index d3fa2d1113..b8f4ff2c1d 100644 --- a/fuzz.go +++ b/fuzz.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build gofuzz -// +build gofuzz package json @@ -12,10 +11,10 @@ import ( ) func Fuzz(data []byte) (score int) { - for _, ctor := range []func() interface{}{ - func() interface{} { return new(interface{}) }, - func() interface{} { return new(map[string]interface{}) }, - func() interface{} { return new([]interface{}) }, + for _, ctor := range []func() any{ + func() any { return new(any) }, + func() any { return new(map[string]any) }, + func() any { return new([]any) }, } { v := ctor() err := Unmarshal(data, v) From e32aa98169da7231fcaae35b2132a8b25f29ca6c Mon Sep 17 00:00:00 2001 From: mrredo <74524695+mrredo@users.noreply.github.com> Date: Tue, 11 Oct 2022 19:34:01 +0300 Subject: [PATCH 6/7] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e3dea142fd..3096c3e703 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/clarketm/json +module github.com/mrredo/json go 1.19 From 2300fccc18627316a160d45efc55bb7b94d956d4 Mon Sep 17 00:00:00 2001 From: mrredo <74524695+mrredo@users.noreply.github.com> Date: Wed, 12 Oct 2022 06:01:37 +0300 Subject: [PATCH 7/7] Update encode.go --- encode.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/encode.go b/encode.go index 18f752efe0..95507c3dc5 100644 --- a/encode.go +++ b/encode.go @@ -356,6 +356,9 @@ func isEmptyValue(v reflect.Value) bool { if vt.Field(i).PkgPath != "" { continue // Private field } + if vt.Field(i).Tag.Get("json") == "-" { + continue + } if !isEmptyValue(v.Field(i)) { return false } @@ -1425,4 +1428,4 @@ func cachedTypeFields(t reflect.Type) structFields { } f, _ := fieldCache.LoadOrStore(t, typeFields(t)) return f.(structFields) -} \ No newline at end of file +}