diff --git a/api.go b/api.go index be0b50bcd..57869de29 100644 --- a/api.go +++ b/api.go @@ -17,16 +17,16 @@ package sonic import ( - `io` + "io" - `github.com/bytedance/sonic/ast` - `github.com/bytedance/sonic/internal/rt` + "github.com/bytedance/sonic/ast" + "github.com/bytedance/sonic/internal/rt" ) const ( - // UseStdJSON indicates you are using fallback implementation (encoding/json) + // UseStdJSON indicates you are using fallback implementation (encoding/json) UseStdJSON = iota - // UseSonicJSON indicates you are using real sonic implementation + // UseSonicJSON indicates you are using real sonic implementation UseSonicJSON ) @@ -35,217 +35,214 @@ const APIKind = apiKind // Config is a combination of sonic/encoder.Options and sonic/decoder.Options type Config struct { - // EscapeHTML indicates encoder to escape all HTML characters - // after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape). - // WARNING: This hurts performance A LOT, USE WITH CARE. - EscapeHTML bool - - // SortMapKeys indicates encoder that the keys of a map needs to be sorted - // before serializing into JSON. - // WARNING: This hurts performance A LOT, USE WITH CARE. - SortMapKeys bool - - // CompactMarshaler indicates encoder that the output JSON from json.Marshaler - // is always compact and needs no validation - CompactMarshaler bool - - // NoQuoteTextMarshaler indicates encoder that the output text from encoding.TextMarshaler - // is always escaped string and needs no quoting - NoQuoteTextMarshaler bool - - // NoNullSliceOrMap indicates encoder that all empty Array or Object are encoded as '[]' or '{}', - // instead of 'null' - NoNullSliceOrMap bool - - // UseInt64 indicates decoder to unmarshal an integer into an interface{} as an - // int64 instead of as a float64. - UseInt64 bool - - // UseNumber indicates decoder to unmarshal a number into an interface{} as a - // json.Number instead of as a float64. - UseNumber bool - - // UseUnicodeErrors indicates decoder to return an error when encounter invalid - // UTF-8 escape sequences. - UseUnicodeErrors bool - - // DisallowUnknownFields indicates 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 bool - - // CopyString indicates decoder to decode string values by copying instead of referring. - CopyString bool - - // ValidateString indicates decoder and encoder to valid string values: decoder will return errors - // when unescaped control chars(\u0000-\u001f) in the string value of JSON. - ValidateString bool - - // NoValidateJSONMarshaler indicates that the encoder should not validate the output string - // after encoding the JSONMarshaler to JSON. - NoValidateJSONMarshaler bool - - // NoValidateJSONSkip indicates the decoder should not validate the JSON value when skipping it, - // such as unknown-fields, mismatched-type, redundant elements.. - NoValidateJSONSkip bool - - // NoEncoderNewline indicates that the encoder should not add a newline after every message - NoEncoderNewline bool - - // Encode Infinity or Nan float into `null`, instead of returning an error. - EncodeNullForInfOrNan bool - - // Int64 or Uint64 into strings on Marshal - Int64ToString bool - Uint64ToString bool + // EscapeHTML indicates encoder to escape all HTML characters + // after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape). + // WARNING: This hurts performance A LOT, USE WITH CARE. + EscapeHTML bool + + // SortMapKeys indicates encoder that the keys of a map needs to be sorted + // before serializing into JSON. + // WARNING: This hurts performance A LOT, USE WITH CARE. + SortMapKeys bool + + // CompactMarshaler indicates encoder that the output JSON from json.Marshaler + // is always compact and needs no validation + CompactMarshaler bool + + // NoQuoteTextMarshaler indicates encoder that the output text from encoding.TextMarshaler + // is always escaped string and needs no quoting + NoQuoteTextMarshaler bool + + // NoNullSliceOrMap indicates encoder that all empty Array or Object are encoded as '[]' or '{}', + // instead of 'null' + NoNullSliceOrMap bool + + // UseInt64 indicates decoder to unmarshal an integer into an interface{} as an + // int64 instead of as a float64. + UseInt64 bool + + // UseNumber indicates decoder to unmarshal a number into an interface{} as a + // json.Number instead of as a float64. + UseNumber bool + + // UseUnicodeErrors indicates decoder to return an error when encounter invalid + // UTF-8 escape sequences. + UseUnicodeErrors bool + + // DisallowUnknownFields indicates 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 bool + + // CopyString indicates decoder to decode string values by copying instead of referring. + CopyString bool + + // ValidateString indicates decoder and encoder to valid string values: decoder will return errors + // when unescaped control chars(\u0000-\u001f) in the string value of JSON. + ValidateString bool + + // NoValidateJSONMarshaler indicates that the encoder should not validate the output string + // after encoding the JSONMarshaler to JSON. + NoValidateJSONMarshaler bool + + // NoValidateJSONSkip indicates the decoder should not validate the JSON value when skipping it, + // such as unknown-fields, mismatched-type, redundant elements.. + NoValidateJSONSkip bool + + // NoEncoderNewline indicates that the encoder should not add a newline after every message + NoEncoderNewline bool + + // Encode Infinity or Nan float into `null`, instead of returning an error. + EncodeNullForInfOrNan bool + + // Int64 or Uint64 into strings on Marshal + Integer64BitToString bool } - + var ( - // ConfigDefault is the default config of APIs, aiming at efficiency and safety. - ConfigDefault = Config{}.Froze() - - // ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json. - ConfigStd = Config{ - EscapeHTML : true, - SortMapKeys: true, - CompactMarshaler: true, - CopyString : true, - ValidateString : true, - }.Froze() - - // ConfigFastest is the fastest config of APIs, aiming at speed. - ConfigFastest = Config{ - NoQuoteTextMarshaler: true, - NoValidateJSONMarshaler: true, - NoValidateJSONSkip: true, - }.Froze() + // ConfigDefault is the default config of APIs, aiming at efficiency and safety. + ConfigDefault = Config{}.Froze() + + // ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json. + ConfigStd = Config{ + EscapeHTML: true, + SortMapKeys: true, + CompactMarshaler: true, + CopyString: true, + ValidateString: true, + }.Froze() + + // ConfigFastest is the fastest config of APIs, aiming at speed. + ConfigFastest = Config{ + NoQuoteTextMarshaler: true, + NoValidateJSONMarshaler: true, + NoValidateJSONSkip: true, + }.Froze() ) - - + // API is a binding of specific config. // This interface is inspired by github.com/json-iterator/go, // and has same behaviors under equavilent config. type API interface { - // MarshalToString returns the JSON encoding string of v - MarshalToString(v interface{}) (string, error) - // Marshal returns the JSON encoding bytes of v. - Marshal(v interface{}) ([]byte, error) - // MarshalIndent returns the JSON encoding bytes with indent and prefix. - MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) - // UnmarshalFromString parses the JSON-encoded bytes and stores the result in the value pointed to by v. - UnmarshalFromString(str string, v interface{}) error - // Unmarshal parses the JSON-encoded string and stores the result in the value pointed to by v. - Unmarshal(data []byte, v interface{}) error - // NewEncoder create a Encoder holding writer - NewEncoder(writer io.Writer) Encoder - // NewDecoder create a Decoder holding reader - NewDecoder(reader io.Reader) Decoder - // Valid validates the JSON-encoded bytes and reports if it is valid - Valid(data []byte) bool + // MarshalToString returns the JSON encoding string of v + MarshalToString(v interface{}) (string, error) + // Marshal returns the JSON encoding bytes of v. + Marshal(v interface{}) ([]byte, error) + // MarshalIndent returns the JSON encoding bytes with indent and prefix. + MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) + // UnmarshalFromString parses the JSON-encoded bytes and stores the result in the value pointed to by v. + UnmarshalFromString(str string, v interface{}) error + // Unmarshal parses the JSON-encoded string and stores the result in the value pointed to by v. + Unmarshal(data []byte, v interface{}) error + // NewEncoder create a Encoder holding writer + NewEncoder(writer io.Writer) Encoder + // NewDecoder create a Decoder holding reader + NewDecoder(reader io.Reader) Decoder + // Valid validates the JSON-encoded bytes and reports if it is valid + Valid(data []byte) bool } // Encoder encodes JSON into io.Writer type Encoder interface { - // Encode writes the JSON encoding of v to the stream, followed by a newline character. - Encode(val interface{}) error - // SetEscapeHTML specifies whether problematic HTML characters - // should be escaped inside JSON quoted strings. - // The default behavior NOT ESCAPE - SetEscapeHTML(on bool) - // SetIndent instructs the encoder to format each subsequent encoded value - // as if indented by the package-level function Indent(dst, src, prefix, indent). - // Calling SetIndent("", "") disables indentation - SetIndent(prefix, indent string) + // Encode writes the JSON encoding of v to the stream, followed by a newline character. + Encode(val interface{}) error + // SetEscapeHTML specifies whether problematic HTML characters + // should be escaped inside JSON quoted strings. + // The default behavior NOT ESCAPE + SetEscapeHTML(on bool) + // SetIndent instructs the encoder to format each subsequent encoded value + // as if indented by the package-level function Indent(dst, src, prefix, indent). + // Calling SetIndent("", "") disables indentation + SetIndent(prefix, indent string) } // Decoder decodes JSON from io.Read type Decoder interface { - // Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v. - Decode(val interface{}) error - // Buffered returns a reader of the data remaining in the Decoder's buffer. - // The reader is valid until the next call to Decode. - Buffered() io.Reader - // 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() - // More reports whether there is another element in the current array or object being parsed. - More() bool - // UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64. - UseNumber() + // Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v. + Decode(val interface{}) error + // Buffered returns a reader of the data remaining in the Decoder's buffer. + // The reader is valid until the next call to Decode. + Buffered() io.Reader + // 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() + // More reports whether there is another element in the current array or object being parsed. + More() bool + // UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64. + UseNumber() } // Marshal returns the JSON encoding bytes of v. func Marshal(val interface{}) ([]byte, error) { - return ConfigDefault.Marshal(val) + return ConfigDefault.Marshal(val) } // 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) { - return ConfigDefault.MarshalIndent(v, prefix, indent) + return ConfigDefault.MarshalIndent(v, prefix, indent) } // MarshalString returns the JSON encoding string of v. func MarshalString(val interface{}) (string, error) { - return ConfigDefault.MarshalToString(val) + return ConfigDefault.MarshalToString(val) } // Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. // NOTICE: This API copies given buffer by default, // if you want to pass JSON more efficiently, use UnmarshalString instead. func Unmarshal(buf []byte, val interface{}) error { - return ConfigDefault.Unmarshal(buf, val) + return ConfigDefault.Unmarshal(buf, val) } // UnmarshalString is like Unmarshal, except buf is a string. func UnmarshalString(buf string, val interface{}) error { - return ConfigDefault.UnmarshalFromString(buf, val) + return ConfigDefault.UnmarshalFromString(buf, val) } // Get searches and locates the given path from src json, // and returns a ast.Node representing the partially json. // // Each path arg must be integer or string: -// - Integer is target index(>=0), means searching current node as array. -// - String is target key, means searching current node as object. +// - Integer is target index(>=0), means searching current node as array. +// - String is target key, means searching current node as object. // -// // Notice: It expects the src json is **Well-formed** and **Immutable** when calling, -// otherwise it may return unexpected result. +// otherwise it may return unexpected result. // Considering memory safety, the returned JSON is **Copied** from the input func Get(src []byte, path ...interface{}) (ast.Node, error) { - return GetCopyFromString(rt.Mem2Str(src), path...) + return GetCopyFromString(rt.Mem2Str(src), path...) } -//GetWithOptions searches and locates the given path from src json, +// GetWithOptions searches and locates the given path from src json, // with specific options of ast.Searcher func GetWithOptions(src []byte, opts ast.SearchOptions, path ...interface{}) (ast.Node, error) { - s := ast.NewSearcher(rt.Mem2Str(src)) - s.SearchOptions = opts - return s.GetByPath(path...) + s := ast.NewSearcher(rt.Mem2Str(src)) + s.SearchOptions = opts + return s.GetByPath(path...) } // GetFromString is same with Get except src is string. // -// WARNING: The returned JSON is **Referenced** from the input. +// WARNING: The returned JSON is **Referenced** from the input. // Caching or long-time holding the returned node may cause OOM. // If your src is big, consider use GetFromStringCopy(). func GetFromString(src string, path ...interface{}) (ast.Node, error) { - return ast.NewSearcher(src).GetByPath(path...) + return ast.NewSearcher(src).GetByPath(path...) } // GetCopyFromString is same with Get except src is string func GetCopyFromString(src string, path ...interface{}) (ast.Node, error) { - return ast.NewSearcher(src).GetByPathCopy(path...) + return ast.NewSearcher(src).GetByPathCopy(path...) } // Valid reports whether data is a valid JSON encoding. func Valid(data []byte) bool { - return ConfigDefault.Valid(data) + return ConfigDefault.Valid(data) } // Valid reports whether data is a valid JSON encoding. func ValidString(data string) bool { - return ConfigDefault.Valid(rt.Str2Mem(data)) + return ConfigDefault.Valid(rt.Str2Mem(data)) } diff --git a/encode_test.go b/encode_test.go index 1740aa771..477652771 100644 --- a/encode_test.go +++ b/encode_test.go @@ -20,70 +20,71 @@ package sonic import ( - `bytes` - `encoding` - `encoding/json` - `fmt` - `log` - `math` - `os` - `reflect` - `regexp` - `runtime` - `runtime/debug` - `strconv` - `testing` - `time` - `unsafe` - `strings` - - `github.com/bytedance/sonic/encoder` - `github.com/stretchr/testify/assert` + "bytes" + "encoding" + "encoding/json" + "fmt" + "log" + "math" + "os" + "reflect" + "regexp" + "runtime" + "runtime/debug" + "strconv" + "strings" + "testing" + "time" + "unsafe" + + "github.com/bytedance/sonic/encoder" + "github.com/stretchr/testify/assert" ) var ( - debugAsyncGC = os.Getenv("SONIC_NO_ASYNC_GC") == "" + debugAsyncGC = os.Getenv("SONIC_NO_ASYNC_GC") == "" ) + func TestMain(m *testing.M) { - go func () { - if !debugAsyncGC { - return - } - println("Begin GC looping...") - for { - runtime.GC() - debug.FreeOSMemory() - } - }() - time.Sleep(time.Millisecond) - m.Run() + go func() { + if !debugAsyncGC { + return + } + println("Begin GC looping...") + for { + runtime.GC() + debug.FreeOSMemory() + } + }() + time.Sleep(time.Millisecond) + m.Run() } type Optionals struct { - Sr string `json:"sr"` - So string `json:"so,omitempty"` - Sw string `json:"-"` + Sr string `json:"sr"` + So string `json:"so,omitempty"` + Sw string `json:"-"` - Ir int `json:"omitempty"` // actually named omitempty, not an option - Io int `json:"io,omitempty"` + Ir int `json:"omitempty"` // actually named omitempty, not an option + Io int `json:"io,omitempty"` - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitempty"` + 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]interface{} `json:"mr"` + Mo map[string]interface{} `json:",omitempty"` - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitempty"` + Fr float64 `json:"fr"` + Fo float64 `json:"fo,omitempty"` - Br bool `json:"br"` - Bo bool `json:"bo,omitempty"` + Br bool `json:"br"` + Bo bool `json:"bo,omitempty"` - Ur uint `json:"ur"` - Uo uint `json:"uo,omitempty"` + Ur uint `json:"ur"` + Uo uint `json:"uo,omitempty"` - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty"` + Str struct{} `json:"str"` + Sto struct{} `json:"sto,omitempty"` } var optionalsExpected = `{ @@ -99,89 +100,89 @@ var optionalsExpected = `{ }` func TestOmitEmpty(t *testing.T) { - var o Optionals - o.Sw = "something" - o.Mr = map[string]interface{}{} - o.Mo = map[string]interface{}{} - - got, err := encoder.EncodeIndented(&o, "", " ", 0) - if err != nil { - t.Fatal(err) - } - if got := string(got); got != optionalsExpected { - t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) - } + var o Optionals + o.Sw = "something" + o.Mr = map[string]interface{}{} + o.Mo = map[string]interface{}{} + + got, err := encoder.EncodeIndented(&o, "", " ", 0) + if err != nil { + t.Fatal(err) + } + if got := string(got); got != optionalsExpected { + t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) + } } type StringTag struct { - BoolStr bool `json:",string"` - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - StrStr string `json:",string"` - NumberStr json.Number `json:",string"` + BoolStr bool `json:",string"` + IntStr int64 `json:",string"` + UintptrStr uintptr `json:",string"` + StrStr string `json:",string"` + NumberStr json.Number `json:",string"` } func TestRoundtripStringTag(t *testing.T) { - tests := []struct { - name string - in StringTag - want string // empty to just test that we roundtrip - }{ - { - name: "AllTypes", - in: StringTag{ - BoolStr: true, - IntStr: 42, - UintptrStr: 44, - StrStr: "xzbit", - NumberStr: "46", - }, - want: `{ + tests := []struct { + name string + in StringTag + want string // empty to just test that we roundtrip + }{ + { + name: "AllTypes", + in: StringTag{ + BoolStr: true, + IntStr: 42, + UintptrStr: 44, + StrStr: "xzbit", + NumberStr: "46", + }, + want: `{ "BoolStr": "true", "IntStr": "42", "UintptrStr": "44", "StrStr": "\"xzbit\"", "NumberStr": "46" }`, - }, - { - // See golang.org/issues/38173. - name: "StringDoubleEscapes", - in: StringTag{ - StrStr: "\b\f\n\r\t\"\\", - NumberStr: "0", // just to satisfy the roundtrip - }, - want: `{ + }, + { + // See golang.org/issues/38173. + name: "StringDoubleEscapes", + in: StringTag{ + StrStr: "\b\f\n\r\t\"\\", + NumberStr: "0", // just to satisfy the roundtrip + }, + want: `{ "BoolStr": "false", "IntStr": "0", "UintptrStr": "0", "StrStr": "\"\\u0008\\u000c\\n\\r\\t\\\"\\\\\"", "NumberStr": "0" }`, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // Indent with a tab prefix to make the multi-line string - // literals in the table nicer to read. - got, err := encoder.EncodeIndented(&test.in, " ", " ", 0) - if err != nil { - t.Fatal(err) - } - if got := string(got); got != test.want { - t.Fatalf(" got: %s\nwant: %s\n", got, test.want) - } - - // Verify that it round-trips. - var s2 StringTag - if err := Unmarshal(got, &s2); err != nil { - t.Fatalf("Decode: %v", err) - } - if !reflect.DeepEqual(test.in, s2) { - t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) - } - }) - } + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Indent with a tab prefix to make the multi-line string + // literals in the table nicer to read. + got, err := encoder.EncodeIndented(&test.in, " ", " ", 0) + if err != nil { + t.Fatal(err) + } + if got := string(got); got != test.want { + t.Fatalf(" got: %s\nwant: %s\n", got, test.want) + } + + // Verify that it round-trips. + var s2 StringTag + if err := Unmarshal(got, &s2); err != nil { + t.Fatalf("Decode: %v", err) + } + if !reflect.DeepEqual(test.in, s2) { + t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) + } + }) + } } // byte slices are special even if they're renamed types. @@ -190,169 +191,169 @@ type renamedByteSlice []byte type renamedRenamedByteSlice []renamedByte func TestEncodeRenamedByteSlice(t *testing.T) { - s := renamedByteSlice("abc") - result, err := Marshal(s) - if err != nil { - t.Fatal(err) - } - expect := `"YWJj"` - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) - } - r := renamedRenamedByteSlice("abc") - result, err = Marshal(r) - if err != nil { - t.Fatal(err) - } - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) - } + s := renamedByteSlice("abc") + result, err := Marshal(s) + if err != nil { + t.Fatal(err) + } + expect := `"YWJj"` + if string(result) != expect { + t.Errorf(" got %s want %s", result, expect) + } + r := renamedRenamedByteSlice("abc") + result, err = Marshal(r) + if err != nil { + t.Fatal(err) + } + if string(result) != expect { + t.Errorf(" got %s want %s", result, expect) + } } type SamePointerNoCycle struct { - Ptr1, Ptr2 *SamePointerNoCycle + Ptr1, Ptr2 *SamePointerNoCycle } var samePointerNoCycle = &SamePointerNoCycle{} type PointerCycle struct { - Ptr *PointerCycle + Ptr *PointerCycle } var pointerCycle = &PointerCycle{} type PointerCycleIndirect struct { - Ptrs []interface{} + Ptrs []interface{} } type RecursiveSlice []RecursiveSlice var ( - pointerCycleIndirect = &PointerCycleIndirect{} - mapCycle = make(map[string]interface{}) - sliceCycle = []interface{}{nil} - sliceNoCycle = []interface{}{nil, nil} - recursiveSliceCycle = []RecursiveSlice{nil} + pointerCycleIndirect = &PointerCycleIndirect{} + mapCycle = make(map[string]interface{}) + sliceCycle = []interface{}{nil} + sliceNoCycle = []interface{}{nil, nil} + recursiveSliceCycle = []RecursiveSlice{nil} ) func init() { - ptr := &SamePointerNoCycle{} - samePointerNoCycle.Ptr1 = ptr - samePointerNoCycle.Ptr2 = ptr - - pointerCycle.Ptr = pointerCycle - pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect} - - mapCycle["x"] = mapCycle - sliceCycle[0] = sliceCycle - sliceNoCycle[1] = sliceNoCycle[:1] - for i := 3; i > 0; i-- { - sliceNoCycle = []interface{}{sliceNoCycle} - } - recursiveSliceCycle[0] = recursiveSliceCycle + ptr := &SamePointerNoCycle{} + samePointerNoCycle.Ptr1 = ptr + samePointerNoCycle.Ptr2 = ptr + + pointerCycle.Ptr = pointerCycle + pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect} + + mapCycle["x"] = mapCycle + sliceCycle[0] = sliceCycle + sliceNoCycle[1] = sliceNoCycle[:1] + for i := 3; i > 0; i-- { + sliceNoCycle = []interface{}{sliceNoCycle} + } + recursiveSliceCycle[0] = recursiveSliceCycle } func TestSamePointerNoCycle(t *testing.T) { - if _, err := Marshal(samePointerNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) - } + if _, err := Marshal(samePointerNoCycle); err != nil { + t.Fatalf("unexpected error: %v", err) + } } func TestSliceNoCycle(t *testing.T) { - if _, err := Marshal(sliceNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) - } + if _, err := Marshal(sliceNoCycle); err != nil { + t.Fatalf("unexpected error: %v", err) + } } var unsupportedValues = []interface{}{ - math.NaN(), - math.Inf(-1), - math.Inf(1), - pointerCycle, - pointerCycleIndirect, - mapCycle, - sliceCycle, - recursiveSliceCycle, + math.NaN(), + math.Inf(-1), + math.Inf(1), + pointerCycle, + pointerCycleIndirect, + mapCycle, + sliceCycle, + recursiveSliceCycle, } func TestUnsupportedValues(t *testing.T) { - for _, v := range unsupportedValues { - if _, err := Marshal(v); err != nil { - if _, ok := err.(*json.UnsupportedValueError); !ok { - t.Errorf("for %v, got %T want UnsupportedValueError", v, err) - } - } else { - t.Errorf("for %v, expected error", v) - } - } + for _, v := range unsupportedValues { + if _, err := Marshal(v); err != nil { + if _, ok := err.(*json.UnsupportedValueError); !ok { + t.Errorf("for %v, got %T want UnsupportedValueError", v, err) + } + } else { + t.Errorf("for %v, expected error", v) + } + } } // Ref has Marshaler and Unmarshaler methods with pointer receiver. type Ref int func (*Ref) MarshalJSON() ([]byte, error) { - return []byte(`"ref"`), nil + return []byte(`"ref"`), nil } func (r *Ref) UnmarshalJSON([]byte) error { - *r = 12 - return nil + *r = 12 + return nil } // Val has Marshaler methods with value receiver. type Val int func (Val) MarshalJSON() ([]byte, error) { - return []byte(`"val"`), nil + return []byte(`"val"`), nil } // RefText has Marshaler and Unmarshaler methods with pointer receiver. type RefText int func (*RefText) MarshalText() ([]byte, error) { - return []byte(`"ref"`), nil + return []byte(`"ref"`), nil } func (r *RefText) UnmarshalText([]byte) error { - *r = 13 - return nil + *r = 13 + return nil } // ValText has Marshaler methods with value receiver. type ValText int func (ValText) MarshalText() ([]byte, error) { - return []byte(`"val"`), nil + return []byte(`"val"`), nil } func TestRefValMarshal(t *testing.T) { - var s = struct { - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - V0 Val - V1 *Val - V2 ValText - V3 *ValText - }{ - R0: 12, - R1: new(Ref), - R2: 14, - R3: new(RefText), - V0: 13, - V1: new(Val), - V2: 15, - V3: new(ValText), - } - const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` - b, err := Marshal(&s) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if got := string(b); got != want { - t.Errorf("got %q, want %q", got, want) - } + var s = struct { + R0 Ref + R1 *Ref + R2 RefText + R3 *RefText + V0 Val + V1 *Val + V2 ValText + V3 *ValText + }{ + R0: 12, + R1: new(Ref), + R2: 14, + R3: new(RefText), + V0: 13, + V1: new(Val), + V2: 15, + V3: new(ValText), + } + const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` + b, err := Marshal(&s) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got := string(b); got != want { + t.Errorf("got %q, want %q", got, want) + } } /* @@ -397,200 +398,200 @@ 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 - }{{ - // 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{} { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - } - ) - return S{S1{1, 2}, S2{3, 4}} - }, - want: `{}`, - }, { - 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{} { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - x, X int - } - ) - return S{S1{1, 2}, S2{3, 4}, 5, 6} - }, - want: `{"X":6}`, - }, { - // Unexported embedded field of non-struct type should not be serialized. - label: "UnexportedEmbeddedInt", - makeInput: func() interface{} { - type ( - myInt int - S struct{ myInt } - ) - return S{5} - }, - want: `{}`, - }, { - // Exported embedded field of non-struct type should be serialized. - label: "ExportedEmbeddedInt", - makeInput: func() interface{} { - type ( - MyInt int - S struct{ MyInt } - ) - return S{5} - }, - want: `{"MyInt":5}`, - }, { - // Unexported embedded field of pointer to non-struct type - // should not be serialized. - label: "UnexportedEmbeddedIntPointer", - makeInput: func() interface{} { - type ( - myInt int - S struct{ *myInt } - ) - s := S{new(myInt)} - *s.myInt = 5 - return s - }, - want: `{}`, - }, { - // Exported embedded field of pointer to non-struct type - // should be serialized. - label: "ExportedEmbeddedIntPointer", - makeInput: func() interface{} { - type ( - MyInt int - S struct{ *MyInt } - ) - s := S{new(MyInt)} - *s.MyInt = 5 - return s - }, - want: `{"MyInt":5}`, - }, { - // Exported fields of embedded structs should have their - // exported fields be serialized regardless of whether the struct types - // themselves are exported. - label: "EmbeddedStruct", - makeInput: func() interface{} { - type ( - s1 struct{ x, X int } - S2 struct{ y, Y int } - S struct { - s1 - S2 - } - ) - return S{s1{1, 2}, S2{3, 4}} - }, - want: `{"X":2,"Y":4}`, - }, { - // Exported fields of pointers to embedded structs should have their - // exported fields be serialized regardless of whether the struct types - // themselves are exported. - label: "EmbeddedStructPointer", - makeInput: func() interface{} { - type ( - s1 struct{ x, X int } - S2 struct{ y, Y int } - S struct { - *s1 - *S2 - } - ) - return S{&s1{1, 2}, &S2{3, 4}} - }, - want: `{"X":2,"Y":4}`, - }, { - // Exported fields on embedded unexported structs at multiple levels - // of nesting should still be serialized. - label: "NestedStructAndInts", - makeInput: func() interface{} { - type ( - MyInt1 int - MyInt2 int - myInt int - s2 struct { - MyInt2 - myInt - } - s1 struct { - MyInt1 - myInt - s2 - } - S struct { - s1 - myInt - } - ) - return S{s1{1, 2, s2{3, 4}}, 6} - }, - want: `{"MyInt1":1,"MyInt2":3}`, - }, { - // If an anonymous struct pointer field is nil, we should ignore - // the embedded fields behind it. Not properly doing so may - // result in the wrong output or reflect panics. - label: "EmbeddedFieldBehindNilPointer", - makeInput: func() interface{} { - type ( - S2 struct{ Field string } - S struct{ *S2 } - ) - return S{} - }, - want: `{}`, - }} - - for _, tt := range tests { - t.Run(tt.label, func(t *testing.T) { - b, err := Marshal(tt.makeInput()) - if err != nil { - t.Fatalf("Marshal() = %v, want nil error", err) - } - if string(b) != tt.want { - t.Fatalf("Marshal() = %q, want %q", b, tt.want) - } - }) - } + tests := []struct { + label string // Test name + makeInput func() interface{} // 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{} { + type ( + S1 struct{ x, X int } + S2 struct{ x, X int } + S struct { + S1 + S2 + } + ) + return S{S1{1, 2}, S2{3, 4}} + }, + want: `{}`, + }, { + 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{} { + type ( + S1 struct{ x, X int } + S2 struct{ x, X int } + S struct { + S1 + S2 + x, X int + } + ) + return S{S1{1, 2}, S2{3, 4}, 5, 6} + }, + want: `{"X":6}`, + }, { + // Unexported embedded field of non-struct type should not be serialized. + label: "UnexportedEmbeddedInt", + makeInput: func() interface{} { + type ( + myInt int + S struct{ myInt } + ) + return S{5} + }, + want: `{}`, + }, { + // Exported embedded field of non-struct type should be serialized. + label: "ExportedEmbeddedInt", + makeInput: func() interface{} { + type ( + MyInt int + S struct{ MyInt } + ) + return S{5} + }, + want: `{"MyInt":5}`, + }, { + // Unexported embedded field of pointer to non-struct type + // should not be serialized. + label: "UnexportedEmbeddedIntPointer", + makeInput: func() interface{} { + type ( + myInt int + S struct{ *myInt } + ) + s := S{new(myInt)} + *s.myInt = 5 + return s + }, + want: `{}`, + }, { + // Exported embedded field of pointer to non-struct type + // should be serialized. + label: "ExportedEmbeddedIntPointer", + makeInput: func() interface{} { + type ( + MyInt int + S struct{ *MyInt } + ) + s := S{new(MyInt)} + *s.MyInt = 5 + return s + }, + want: `{"MyInt":5}`, + }, { + // Exported fields of embedded structs should have their + // exported fields be serialized regardless of whether the struct types + // themselves are exported. + label: "EmbeddedStruct", + makeInput: func() interface{} { + type ( + s1 struct{ x, X int } + S2 struct{ y, Y int } + S struct { + s1 + S2 + } + ) + return S{s1{1, 2}, S2{3, 4}} + }, + want: `{"X":2,"Y":4}`, + }, { + // Exported fields of pointers to embedded structs should have their + // exported fields be serialized regardless of whether the struct types + // themselves are exported. + label: "EmbeddedStructPointer", + makeInput: func() interface{} { + type ( + s1 struct{ x, X int } + S2 struct{ y, Y int } + S struct { + *s1 + *S2 + } + ) + return S{&s1{1, 2}, &S2{3, 4}} + }, + want: `{"X":2,"Y":4}`, + }, { + // Exported fields on embedded unexported structs at multiple levels + // of nesting should still be serialized. + label: "NestedStructAndInts", + makeInput: func() interface{} { + type ( + MyInt1 int + MyInt2 int + myInt int + s2 struct { + MyInt2 + myInt + } + s1 struct { + MyInt1 + myInt + s2 + } + S struct { + s1 + myInt + } + ) + return S{s1{1, 2, s2{3, 4}}, 6} + }, + want: `{"MyInt1":1,"MyInt2":3}`, + }, { + // If an anonymous struct pointer field is nil, we should ignore + // the embedded fields behind it. Not properly doing so may + // result in the wrong output or reflect panics. + label: "EmbeddedFieldBehindNilPointer", + makeInput: func() interface{} { + type ( + S2 struct{ Field string } + S struct{ *S2 } + ) + return S{} + }, + want: `{}`, + }} + + for _, tt := range tests { + t.Run(tt.label, func(t *testing.T) { + b, err := Marshal(tt.makeInput()) + if err != nil { + t.Fatalf("Marshal() = %v, want nil error", err) + } + if string(b) != tt.want { + t.Fatalf("Marshal() = %q, want %q", b, tt.want) + } + }) + } } type BugA struct { - S string + S string } type BugB struct { - BugA - S string + BugA + S string } type BugC struct { - S string + S string } // Legal Go: We never use the repeated embedded field (S). type BugX struct { - A int - BugA - BugB + A int + BugA + BugB } // golang.org/issue/16042. @@ -599,10 +600,10 @@ type BugX struct { type nilJSONMarshaler string func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { - if nm == nil { - return Marshal("0zenil0") - } - return Marshal("zenil:" + string(*nm)) + if nm == nil { + return Marshal("0zenil0") + } + return Marshal("zenil:" + string(*nm)) } // golang.org/issue/34235. @@ -611,214 +612,214 @@ func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { type nilTextMarshaler string func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { - if nm == nil { - return []byte("0zenil0"), nil - } - return []byte("zenil:" + string(*nm)), nil + if nm == nil { + return []byte("0zenil0"), nil + } + return []byte("zenil:" + string(*nm)), nil } // See golang.org/issue/16042 and golang.org/issue/34235. func TestNilMarshal(t *testing.T) { - testCases := []struct { - v interface{} - want string - }{ - {v: nil, want: `null`}, - {v: new(float64), want: `0`}, - {v: []interface{}(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 json.Marshaler }{}, want: `{"M":null}`}, - {v: struct{ M json.Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M interface{} }{(*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}`}, - } - - for _, tt := range testCases { - out, err := Marshal(tt.v) - if err != nil || string(out) != tt.want { - t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) - continue - } - } + testCases := []struct { + v interface{} + want string + }{ + {v: nil, want: `null`}, + {v: new(float64), want: `0`}, + {v: []interface{}(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 json.Marshaler }{}, want: `{"M":null}`}, + {v: struct{ M json.Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, + {v: struct{ M interface{} }{(*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}`}, + } + + for _, tt := range testCases { + out, err := Marshal(tt.v) + if err != nil || string(out) != tt.want { + t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) + continue + } + } } // Issue 5245. func TestEmbeddedBug(t *testing.T) { - v := BugB{ - BugA{"A"}, - "B", - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"S":"B"}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } - // Now check that the duplicate field, S, does not appear. - x := BugX{ - A: 23, - } - b, err = Marshal(x) - if err != nil { - t.Fatal("Marshal:", err) - } - want = `{"A":23}` - got = string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := BugB{ + BugA{"A"}, + "B", + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"S":"B"}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } + // Now check that the duplicate field, S, does not appear. + x := BugX{ + A: 23, + } + b, err = Marshal(x) + if err != nil { + t.Fatal("Marshal:", err) + } + want = `{"A":23}` + got = string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } type BugD struct { // Same as BugA after tagging. - XXX string `json:"S"` + XXX string `json:"S"` } // BugD's tagged S field should dominate BugA's. type BugY struct { - BugA - BugD + BugA + BugD } // Test that a field with a tag dominates untagged fields. func TestTaggedFieldDominates(t *testing.T) { - v := BugY{ - BugA{"BugA"}, - BugD{"BugD"}, - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"S":"BugD"}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := BugY{ + BugA{"BugA"}, + BugD{"BugD"}, + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"S":"BugD"}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } // There are no tags here, so S should not appear. type BugZ struct { - BugA - BugC - BugY // Contains a tagged S field through BugD; should not dominate. + BugA + BugC + BugY // Contains a tagged S field through BugD; should not dominate. } func TestDuplicatedFieldDisappears(t *testing.T) { - v := BugZ{ - BugA{"BugA"}, - BugC{"BugC"}, - BugY{ - BugA{"nested BugA"}, - BugD{"nested BugD"}, - }, - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := BugZ{ + BugA{"BugA"}, + BugC{"BugC"}, + BugY{ + BugA{"nested BugA"}, + BugD{"nested BugD"}, + }, + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } func TestStdLibIssue10281(t *testing.T) { - type Foo struct { - N json.Number - } - x := Foo{json.Number(`invalid`)} - - b, err := Marshal(&x) - if err == nil { - t.Errorf("Marshal(&x) = %#q; want error", b) - } + type Foo struct { + N json.Number + } + x := Foo{json.Number(`invalid`)} + + b, err := Marshal(&x) + if err == nil { + t.Errorf("Marshal(&x) = %#q; want error", b) + } } // golang.org/issue/8582 func TestEncodePointerString(t *testing.T) { - type stringPointer struct { - N *int64 `json:"n,string"` - } - var n int64 = 42 - b, err := Marshal(stringPointer{N: &n}) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if got, want := string(b), `{"n":"42"}`; got != want { - t.Errorf("Marshal = %s, want %s", got, want) - } - var back stringPointer - err = Unmarshal(b, &back) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if back.N == nil { - t.Fatalf("Unmarshaled nil N field") - } - if *back.N != 42 { - t.Fatalf("*N = %d; want 42", *back.N) - } + type stringPointer struct { + N *int64 `json:"n,string"` + } + var n int64 = 42 + b, err := Marshal(stringPointer{N: &n}) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got, want := string(b), `{"n":"42"}`; got != want { + t.Errorf("Marshal = %s, want %s", got, want) + } + var back stringPointer + err = Unmarshal(b, &back) + if err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if back.N == nil { + t.Fatalf("Unmarshaled nil N field") + } + if *back.N != 42 { + t.Fatalf("*N = %d; want 42", *back.N) + } } var encodeStringTests = []struct { - in string - out string + in string + out string }{ - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\u0008"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\u000c"`}, - {"\x0d", `"\r"`}, - {"\x0e", `"\u000e"`}, - {"\x0f", `"\u000f"`}, - {"\x10", `"\u0010"`}, - {"\x11", `"\u0011"`}, - {"\x12", `"\u0012"`}, - {"\x13", `"\u0013"`}, - {"\x14", `"\u0014"`}, - {"\x15", `"\u0015"`}, - {"\x16", `"\u0016"`}, - {"\x17", `"\u0017"`}, - {"\x18", `"\u0018"`}, - {"\x19", `"\u0019"`}, - {"\x1a", `"\u001a"`}, - {"\x1b", `"\u001b"`}, - {"\x1c", `"\u001c"`}, - {"\x1d", `"\u001d"`}, - {"\x1e", `"\u001e"`}, - {"\x1f", `"\u001f"`}, + {"\x00", `"\u0000"`}, + {"\x01", `"\u0001"`}, + {"\x02", `"\u0002"`}, + {"\x03", `"\u0003"`}, + {"\x04", `"\u0004"`}, + {"\x05", `"\u0005"`}, + {"\x06", `"\u0006"`}, + {"\x07", `"\u0007"`}, + {"\x08", `"\u0008"`}, + {"\x09", `"\t"`}, + {"\x0a", `"\n"`}, + {"\x0b", `"\u000b"`}, + {"\x0c", `"\u000c"`}, + {"\x0d", `"\r"`}, + {"\x0e", `"\u000e"`}, + {"\x0f", `"\u000f"`}, + {"\x10", `"\u0010"`}, + {"\x11", `"\u0011"`}, + {"\x12", `"\u0012"`}, + {"\x13", `"\u0013"`}, + {"\x14", `"\u0014"`}, + {"\x15", `"\u0015"`}, + {"\x16", `"\u0016"`}, + {"\x17", `"\u0017"`}, + {"\x18", `"\u0018"`}, + {"\x19", `"\u0019"`}, + {"\x1a", `"\u001a"`}, + {"\x1b", `"\u001b"`}, + {"\x1c", `"\u001c"`}, + {"\x1d", `"\u001d"`}, + {"\x1e", `"\u001e"`}, + {"\x1f", `"\u001f"`}, } func TestEncodeString(t *testing.T) { - for _, tt := range encodeStringTests { - b, err := Marshal(tt.in) - if err != nil { - t.Errorf("Marshal(%q): %v", tt.in, err) - continue - } - out := string(b) - if out != tt.out { - t.Errorf("Marshal(%q) = %#q, want %#q", tt.in, out, tt.out) - } - } + for _, tt := range encodeStringTests { + b, err := Marshal(tt.in) + if err != nil { + t.Errorf("Marshal(%q): %v", tt.in, err) + continue + } + out := string(b) + if out != tt.out { + t.Errorf("Marshal(%q) = %#q, want %#q", tt.in, out, tt.out) + } + } } type jsonbyte byte @@ -838,255 +839,255 @@ type textint int func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } func tenc(format string, a ...interface{}) ([]byte, error) { - var buf bytes.Buffer - _, _ = fmt.Fprintf(&buf, format, a...) - return buf.Bytes(), nil + var buf bytes.Buffer + _, _ = fmt.Fprintf(&buf, format, a...) + return buf.Bytes(), nil } // Issue 13783 func TestEncodeBytekind(t *testing.T) { - testdata := []struct { - data interface{} - want string - }{ - {byte(7), "7"}, - {jsonbyte(7), `{"JB":7}`}, - {textbyte(4), `"TB:4"`}, - {jsonint(5), `{"JI":5}`}, - {textint(1), `"TI:1"`}, - {[]byte{0, 1}, `"AAE="`}, - {[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, - {[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, - {[]textbyte{2, 3}, `["TB:2","TB:3"]`}, - {[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, - {[]textint{9, 3}, `["TI:9","TI:3"]`}, - {[]int{9, 3}, `[9,3]`}, - } - for _, d := range testdata { - js, err := Marshal(d.data) - if err != nil { - t.Error(err) - continue - } - got, want := string(js), d.want - if got != want { - t.Errorf("got %s, want %s", got, want) - } - } + testdata := []struct { + data interface{} + want string + }{ + {byte(7), "7"}, + {jsonbyte(7), `{"JB":7}`}, + {textbyte(4), `"TB:4"`}, + {jsonint(5), `{"JI":5}`}, + {textint(1), `"TI:1"`}, + {[]byte{0, 1}, `"AAE="`}, + {[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, + {[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, + {[]textbyte{2, 3}, `["TB:2","TB:3"]`}, + {[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, + {[]textint{9, 3}, `["TI:9","TI:3"]`}, + {[]int{9, 3}, `[9,3]`}, + } + for _, d := range testdata { + js, err := Marshal(d.data) + if err != nil { + t.Error(err) + continue + } + got, want := string(js), d.want + if got != want { + t.Errorf("got %s, want %s", got, want) + } + } } // https://golang.org/issue/33675 func TestNilMarshalerTextMapKey(t *testing.T) { - b, err := Marshal(map[*unmarshalerText]int{ - (*unmarshalerText)(nil): 1, - }) - if err != nil { - t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) - } - const want = `{"":1}` - if string(b) != want { - t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, want) - } + b, err := Marshal(map[*unmarshalerText]int{ + (*unmarshalerText)(nil): 1, + }) + if err != nil { + t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) + } + const want = `{"":1}` + if string(b) != want { + t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, want) + } } var re = regexp.MustCompile // syntactic checks on form of marshaled floating point numbers. var badFloatREs = []*regexp.Regexp{ - re(`p`), // no binary exponential notation - re(`^\+`), // no leading + sign - re(`^-?0[^.]`), // no unnecessary leading zeros - re(`^-?\.`), // leading zero required before decimal point - re(`\.(e|$)`), // no trailing decimal - re(`\.[0-9]+0(e|$)`), // no trailing zero in fraction - re(`^-?(0|[0-9]{2,})\..*e`), // exponential notation must have normalized mantissa - re(`e[+-]0`), // exponent must not have leading zeros - re(`e-[1-6]$`), // not tiny enough for exponential notation - re(`e+(.|1.|20)$`), // not big enough for exponential notation - re(`^-?0\.0000000`), // too tiny, should use exponential notation - re(`^-?[0-9]{22}`), // too big, should use exponential notation - re(`[1-9][0-9]{16}[1-9]`), // too many significant digits in integer - re(`[1-9][0-9.]{17}[1-9]`), // too many significant digits in decimal + re(`p`), // no binary exponential notation + re(`^\+`), // no leading + sign + re(`^-?0[^.]`), // no unnecessary leading zeros + re(`^-?\.`), // leading zero required before decimal point + re(`\.(e|$)`), // no trailing decimal + re(`\.[0-9]+0(e|$)`), // no trailing zero in fraction + re(`^-?(0|[0-9]{2,})\..*e`), // exponential notation must have normalized mantissa + re(`e[+-]0`), // exponent must not have leading zeros + re(`e-[1-6]$`), // not tiny enough for exponential notation + re(`e+(.|1.|20)$`), // not big enough for exponential notation + re(`^-?0\.0000000`), // too tiny, should use exponential notation + re(`^-?[0-9]{22}`), // too big, should use exponential notation + re(`[1-9][0-9]{16}[1-9]`), // too many significant digits in integer + re(`[1-9][0-9.]{17}[1-9]`), // too many significant digits in decimal } func TestMarshalFloat(t *testing.T) { - t.Parallel() - nfail := 0 - test := func(f float64, bits int) { - vf := interface{}(f) - if bits == 32 { - f = float64(float32(f)) // round - vf = float32(f) - } - bout, err := Marshal(vf) - if err != nil { - t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) - nfail++ - return - } - out := string(bout) - - // result must convert back to the same float - g, err := strconv.ParseFloat(out, bits) - if err != nil { - t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err) - nfail++ - return - } - if f != g { - t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) - nfail++ - return - } - - for _, re := range badFloatREs { - if re.MatchString(out) { - t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re) - nfail++ - return - } - } - } - - var ( - bigger = math.Inf(+1) - smaller = math.Inf(-1) - ) - - var digits = "1.2345678901234567890123" - for i := len(digits); i >= 2; i-- { - if testing.Short() && i < len(digits)-4 { - break - } - for exp := -30; exp <= 30; exp++ { - for _, sign := range "+-" { - for bits := 32; bits <= 64; bits += 32 { - s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp) - f, err := strconv.ParseFloat(s, bits) - if err != nil { - log.Fatal(err) - } - next := math.Nextafter - if bits == 32 { - next = func(g, h float64) float64 { - return float64(math.Nextafter32(float32(g), float32(h))) - } - } - test(f, bits) - test(next(f, bigger), bits) - test(next(f, smaller), bits) - if nfail > 50 { - t.Fatalf("stopping test early") - } - } - } - } - } - test(0, 64) - test(math.Copysign(0, -1), 64) - test(0, 32) - test(math.Copysign(0, -1), 32) + t.Parallel() + nfail := 0 + test := func(f float64, bits int) { + vf := interface{}(f) + if bits == 32 { + f = float64(float32(f)) // round + vf = float32(f) + } + bout, err := Marshal(vf) + if err != nil { + t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) + nfail++ + return + } + out := string(bout) + + // result must convert back to the same float + g, err := strconv.ParseFloat(out, bits) + if err != nil { + t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err) + nfail++ + return + } + if f != g { + t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) + nfail++ + return + } + + for _, re := range badFloatREs { + if re.MatchString(out) { + t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re) + nfail++ + return + } + } + } + + var ( + bigger = math.Inf(+1) + smaller = math.Inf(-1) + ) + + var digits = "1.2345678901234567890123" + for i := len(digits); i >= 2; i-- { + if testing.Short() && i < len(digits)-4 { + break + } + for exp := -30; exp <= 30; exp++ { + for _, sign := range "+-" { + for bits := 32; bits <= 64; bits += 32 { + s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp) + f, err := strconv.ParseFloat(s, bits) + if err != nil { + log.Fatal(err) + } + next := math.Nextafter + if bits == 32 { + next = func(g, h float64) float64 { + return float64(math.Nextafter32(float32(g), float32(h))) + } + } + test(f, bits) + test(next(f, bigger), bits) + test(next(f, smaller), bits) + if nfail > 50 { + t.Fatalf("stopping test early") + } + } + } + } + } + test(0, 64) + test(math.Copysign(0, -1), 64) + test(0, 32) + test(math.Copysign(0, -1), 32) } func TestMarshalRawMessageValue(t *testing.T) { - type ( - T1 struct { - M json.RawMessage `json:",omitempty"` - } - T2 struct { - M *json.RawMessage `json:",omitempty"` - } - ) - - var ( - rawNil = json.RawMessage(nil) - rawEmpty = json.RawMessage([]byte{}) - rawText = json.RawMessage(`"foo"`) - ) - - tests := []struct { - in interface{} - 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}, - {struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, - {&struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, - {struct{ M *json.RawMessage }{&rawNil}, `{"M":null}`, true}, - {&struct{ M *json.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}, - {T1{rawNil}, "{}", true}, - {T2{&rawNil}, `{"M":null}`, true}, - {&T1{rawNil}, "{}", true}, - {&T2{&rawNil}, `{"M":null}`, true}, - - // Test with empty, but non-nil, RawMessage. - {rawEmpty, "", false}, - {&rawEmpty, "", false}, - {[]interface{}{rawEmpty}, "", false}, - {&[]interface{}{rawEmpty}, "", false}, - {[]interface{}{&rawEmpty}, "", false}, - {&[]interface{}{&rawEmpty}, "", false}, - {struct{ X json.RawMessage }{rawEmpty}, "", false}, - {&struct{ X json.RawMessage }{rawEmpty}, "", false}, - {struct{ X *json.RawMessage }{&rawEmpty}, "", false}, - {&struct{ X *json.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}, - {T1{rawEmpty}, "{}", true}, - {T2{&rawEmpty}, "", false}, - {&T1{rawEmpty}, "{}", true}, - {&T2{&rawEmpty}, "", false}, - - // Test with RawMessage with some text. - // - // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". - // This behavior was intentionally changed in Go 1.8. - // 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}, - {struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, - {struct{ M *json.RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {&struct{ M *json.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}, - {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {T2{&rawText}, `{"M":"foo"}`, true}, - {&T1{rawText}, `{"M":"foo"}`, true}, - {&T2{&rawText}, `{"M":"foo"}`, true}, - } - - for i, tt := range tests { - b, err := Marshal(tt.in) - if ok := err == nil; ok != tt.ok { - if err != nil { - t.Errorf("test %d, unexpected failure: %v", i, err) - } else { - t.Errorf("test %d, unexpected success", i) - } - } - if got := string(b); got != tt.want { - t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) - } - } + type ( + T1 struct { + M json.RawMessage `json:",omitempty"` + } + T2 struct { + M *json.RawMessage `json:",omitempty"` + } + ) + + var ( + rawNil = json.RawMessage(nil) + rawEmpty = json.RawMessage([]byte{}) + rawText = json.RawMessage(`"foo"`) + ) + + tests := []struct { + in interface{} + 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}, + {struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, + {&struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, + {struct{ M *json.RawMessage }{&rawNil}, `{"M":null}`, true}, + {&struct{ M *json.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}, + {T1{rawNil}, "{}", true}, + {T2{&rawNil}, `{"M":null}`, true}, + {&T1{rawNil}, "{}", true}, + {&T2{&rawNil}, `{"M":null}`, true}, + + // Test with empty, but non-nil, RawMessage. + {rawEmpty, "", false}, + {&rawEmpty, "", false}, + {[]interface{}{rawEmpty}, "", false}, + {&[]interface{}{rawEmpty}, "", false}, + {[]interface{}{&rawEmpty}, "", false}, + {&[]interface{}{&rawEmpty}, "", false}, + {struct{ X json.RawMessage }{rawEmpty}, "", false}, + {&struct{ X json.RawMessage }{rawEmpty}, "", false}, + {struct{ X *json.RawMessage }{&rawEmpty}, "", false}, + {&struct{ X *json.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}, + {T1{rawEmpty}, "{}", true}, + {T2{&rawEmpty}, "", false}, + {&T1{rawEmpty}, "{}", true}, + {&T2{&rawEmpty}, "", false}, + + // Test with RawMessage with some text. + // + // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". + // This behavior was intentionally changed in Go 1.8. + // 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}, + {struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {&struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, + {struct{ M *json.RawMessage }{&rawText}, `{"M":"foo"}`, true}, + {&struct{ M *json.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}, + {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {T2{&rawText}, `{"M":"foo"}`, true}, + {&T1{rawText}, `{"M":"foo"}`, true}, + {&T2{&rawText}, `{"M":"foo"}`, true}, + } + + for i, tt := range tests { + b, err := Marshal(tt.in) + if ok := err == nil; ok != tt.ok { + if err != nil { + t.Errorf("test %d, unexpected failure: %v", i, err) + } else { + t.Errorf("test %d, unexpected success", i) + } + } + if got := string(b); got != tt.want { + t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) + } + } } type marshalPanic struct{} @@ -1094,229 +1095,240 @@ type marshalPanic struct{} func (marshalPanic) MarshalJSON() ([]byte, error) { panic(0xdead) } func TestMarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - _, _ = Marshal(&marshalPanic{}) - t.Error("Marshal should have panicked") + defer func() { + if got := recover(); !reflect.DeepEqual(got, 0xdead) { + t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) + } + }() + _, _ = Marshal(&marshalPanic{}) + t.Error("Marshal should have panicked") } //goland:noinspection NonAsciiCharacters func TestMarshalUncommonFieldNames(t *testing.T) { - v := struct { - A0, À, Aβ int - }{} - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"A0":0,"À":0,"Aβ":0}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := struct { + A0, À, Aβ int + }{} + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"A0":0,"À":0,"Aβ":0}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } type DummyMarshalerError struct { - Type reflect.Type - Err error - SourceFunc string + Type reflect.Type + Err error + SourceFunc string } func (self *DummyMarshalerError) err() *json.MarshalerError { - return (*json.MarshalerError)(unsafe.Pointer(self)) + return (*json.MarshalerError)(unsafe.Pointer(self)) } func TestMarshalerError(t *testing.T) { - s := "test variable" - st := reflect.TypeOf(s) - errText := "json: test error" - - tests := []struct { - err *json.MarshalerError - want string - }{ - { - (&DummyMarshalerError{st, fmt.Errorf(errText), ""}).err(), - "json: error calling MarshalJSON for type " + st.String() + ": " + errText, - }, - { - (&DummyMarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}).err(), - "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, - }, - } - - for i, tt := range tests { - got := tt.err.Error() - if got != tt.want { - t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) - } - } + s := "test variable" + st := reflect.TypeOf(s) + errText := "json: test error" + + tests := []struct { + err *json.MarshalerError + want string + }{ + { + (&DummyMarshalerError{st, fmt.Errorf(errText), ""}).err(), + "json: error calling MarshalJSON for type " + st.String() + ": " + errText, + }, + { + (&DummyMarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}).err(), + "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, + }, + } + + for i, tt := range tests { + got := tt.err.Error() + if got != tt.want { + t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) + } + } } func TestMarshalNullNil(t *testing.T) { - var v = struct { - A []int - B map[string]int - }{} - o, e := Marshal(v) - assert.Nil(t, e) - assert.Equal(t, `{"A":null,"B":null}`, string(o)) - o, e = Config{ - NoNullSliceOrMap: true, - }.Froze().Marshal(v) - assert.Nil(t, e) - assert.Equal(t, `{"A":[],"B":{}}`, string(o)) + var v = struct { + A []int + B map[string]int + }{} + o, e := Marshal(v) + assert.Nil(t, e) + assert.Equal(t, `{"A":null,"B":null}`, string(o)) + o, e = Config{ + NoNullSliceOrMap: true, + }.Froze().Marshal(v) + assert.Nil(t, e) + assert.Equal(t, `{"A":[],"B":{}}`, string(o)) } func TestEncoder_LongestInvalidUtf8(t *testing.T) { - for _, data := range([]string{ - "\"" + strings.Repeat("\x80", 4096) + "\"", - "\"" + strings.Repeat("\x80", 4095) + "\"", - "\"" + strings.Repeat("\x80", 4097) + "\"", - "\"" + strings.Repeat("\x80", 12345) + "\"", - }) { - testEncodeInvalidUtf8(t, []byte(data)) - } + for _, data := range []string{ + "\"" + strings.Repeat("\x80", 4096) + "\"", + "\"" + strings.Repeat("\x80", 4095) + "\"", + "\"" + strings.Repeat("\x80", 4097) + "\"", + "\"" + strings.Repeat("\x80", 12345) + "\"", + } { + testEncodeInvalidUtf8(t, []byte(data)) + } } func testEncodeInvalidUtf8(t *testing.T, data []byte) { - jgot, jerr := json.Marshal(data) - sgot, serr := ConfigStd.Marshal(data) - assert.Equal(t, serr != nil, jerr != nil) - if jerr == nil { - assert.Equal(t, sgot, jgot) - } + jgot, jerr := json.Marshal(data) + sgot, serr := ConfigStd.Marshal(data) + assert.Equal(t, serr != nil, jerr != nil) + if jerr == nil { + assert.Equal(t, sgot, jgot) + } } func TestEncoder_RandomInvalidUtf8(t *testing.T) { - nums := 1000 - maxLen := 1000 - for i := 0; i < nums; i++ { - testEncodeInvalidUtf8(t, genRandJsonBytes(maxLen)) - testEncodeInvalidUtf8(t, genRandJsonRune(maxLen)) - } + nums := 1000 + maxLen := 1000 + for i := 0; i < nums; i++ { + testEncodeInvalidUtf8(t, genRandJsonBytes(maxLen)) + testEncodeInvalidUtf8(t, genRandJsonRune(maxLen)) + } } func TestMarshalInfOrNan(t *testing.T) { - tests := [] interface{}{ - math.Inf(1), math.Inf(-1), math.NaN(), float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), []interface{}{math.Inf(1), math.Inf(-1), math.NaN(), float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN())}, + tests := []interface{}{ + math.Inf(1), math.Inf(-1), math.NaN(), float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), []interface{}{math.Inf(1), math.Inf(-1), math.NaN(), float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN())}, []float64{math.Inf(1), math.Inf(-1), math.NaN()}, []float32{float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN())}, - } - - allowNanInf := Config { - EncodeNullForInfOrNan: true, - }.Froze() - for _, tt := range tests { - b, err := allowNanInf.Marshal(tt) - assert.Nil(t, err) - if len(b) == 4 { - assert.Equal(t, string(b), "null") - } else { - println(string(b)) - } - - b, err = Marshal(tt) - assert.NotNil(t, err) - assert.True(t, strings.Contains(err.Error(), "json: unsupported value: NaN or ±Infinite")) - } + } + + allowNanInf := Config{ + EncodeNullForInfOrNan: true, + }.Froze() + for _, tt := range tests { + b, err := allowNanInf.Marshal(tt) + assert.Nil(t, err) + if len(b) == 4 { + assert.Equal(t, string(b), "null") + } else { + println(string(b)) + } + + b, err = Marshal(tt) + assert.NotNil(t, err) + assert.True(t, strings.Contains(err.Error(), "json: unsupported value: NaN or ±Infinite")) + } } -func TestInt64OrUint64ToString(t *testing.T){ - cases := []struct{ - name string - val any - exceptAllTrue string - exceptInt64ToString string - exceptUint64ToString string - }{ - { - name: "normal_map", - val: map[string]any{ - "int": int(12), - "int64": int64(34), - "uint64": uint64(56), - }, - exceptAllTrue: `{"int":12,"int64":"34","uint64":"56"}`, - exceptInt64ToString: `{"int":12,"int64":"34","uint64":56}`, - exceptUint64ToString: `{"int":12,"int64":34,"uint64":"56"}`, - }, - { - name: "int_key_map", - val:map[int64]any{ - int64(12): int(12), - int64(34): int64(34), - int64(56): uint64(56), - }, - exceptAllTrue: `{"12":12,"34":"34","56":"56"}`, - exceptInt64ToString: `{"12":12,"34":"34","56":56}`, - exceptUint64ToString: `{"12":12,"34":34,"56":"56"}`, - }, - { - name: "normal_struct", - val: struct{ - Int int `json:"int"` - Int64 int64 `json:"int64"` - Uint64 uint64 `json:"uint64"` - }{ - Int: int(12), - Int64: int64(34), - Uint64: uint64(56), - }, - exceptAllTrue: `{"int":12,"int64":"34","uint64":"56"}`, - exceptInt64ToString: `{"int":12,"int64":"34","uint64":56}`, - exceptUint64ToString: `{"int":12,"int64":34,"uint64":"56"}`, - }, - { - name: "normal_slice", - val: []any{ - int(12), int64(34), uint64(56), - }, - exceptAllTrue: `[12,"34","56"]`, - exceptInt64ToString: `[12,"34",56]`, - exceptUint64ToString: `[12,34,"56"]`, - }, - { - name: "single_int64", - val: int64(34), - exceptAllTrue: `"34"`, - exceptInt64ToString: `"34"`, - exceptUint64ToString: `34`, - }, - { - name: "single_uint64", - val: uint64(56), - exceptAllTrue: `"56"`, - exceptInt64ToString: `56`, - exceptUint64ToString: `"56"`, - }, - } - - check := func(t *testing.T, except string, testRes []byte) { - var tmp1 any - assert.Nil(t, Unmarshal([]byte(testRes), &tmp1)) - var tmp2 any - assert.Nil(t, Unmarshal([]byte(except), &tmp2)) - assert.Equal(t, tmp2, tmp1) - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - b, e := Config{Int64ToString: true, Uint64ToString: true}.Froze().Marshal(c.val) - assert.Nil(t, e) - check(t, c.exceptAllTrue, b) - - b, e = Config{Int64ToString: true}.Froze().Marshal(c.val) - assert.Nil(t, e) - check(t, c.exceptInt64ToString, b) - - b, e = Config{Uint64ToString: true}.Froze().Marshal(c.val) - assert.Nil(t, e) - check(t, c.exceptUint64ToString, b) - }) - } -} \ No newline at end of file +func TestInt64OrUInteger64BitToString(t *testing.T) { + int64ptr := int64(432556670863027541) + uint64ptr := uint64(12372850276778298372) + cases := []struct { + name string + val any + exceptTrue string + exceptFalse string + }{ + { + name: "normal_map", + val: map[string]any{ + "int": int(12), + "int64": int64(34), + "uint64": uint64(56), + }, + exceptTrue: `{"int":12,"int64":"34","uint64":"56"}`, + exceptFalse: `{"int":12,"int64":34,"uint64":56}`, + }, + { + name: "int_key_map", + val: map[int64]any{ + int64(12): int(12), + int64(34): int64(34), + int64(56): uint64(56), + }, + exceptTrue: `{"12":12,"34":"34","56":"56"}`, + exceptFalse: `{"12":12,"34":34,"56":56}`, + }, + { + name: "normal_struct", + val: struct { + Int int `json:"int"` + Int64 int64 `json:"int64"` + Uint64 uint64 `json:"uint64"` + }{ + Int: int(12), + Int64: int64(34), + Uint64: uint64(56), + }, + exceptTrue: `{"int":12,"int64":"34","uint64":"56"}`, + exceptFalse: `{"int":12,"int64":34,"uint64":56}`, + }, + { + name: "normal_slice", + val: []any{ + int(12), int64(34), uint64(56), + }, + exceptTrue: `[12,"34","56"]`, + exceptFalse: `[12,34,56]`, + }, + { + name: "single_int64", + val: int64(34), + exceptTrue: `"34"`, + exceptFalse: `34`, + }, + { + name: "single_uint64", + val: uint64(56), + exceptTrue: `"56"`, + exceptFalse: `56`, + }, + { + name: "int64ptr", + val: struct { + Map map[string]any + }{map[string]any{"val": struct { + Int64Ptr any + Uint64Ptr any + Int64 any + Uint64 any + }{ + Int64Ptr: &int64ptr, + Uint64Ptr: &uint64ptr, + Int64: int64(123), + Uint64: uint64(456), + }}}, + exceptTrue: `{"Map":{"val":{"Int64Ptr":"432556670863027541",` + + `"Uint64Ptr":"12372850276778298372","Int64":"123","Uint64":"456"}}}`, + exceptFalse: `{"Map":{"val":{"Int64Ptr":432556670863027541,` + + `"Uint64Ptr":12372850276778298372,"Int64":123,"Uint64":456}}}`, + }, + } + + check := func(t *testing.T, except string, testRes []byte) { + var tmp1 any + assert.Nil(t, Unmarshal([]byte(testRes), &tmp1)) + var tmp2 any + assert.Nil(t, Unmarshal([]byte(except), &tmp2)) + assert.Equal(t, tmp2, tmp1) + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + b, e := Config{Integer64BitToString: true}.Froze().Marshal(c.val) + assert.Nil(t, e) + check(t, c.exceptTrue, b) + + b, e = Config{Integer64BitToString: false}.Froze().Marshal(c.val) + assert.Nil(t, e) + check(t, c.exceptFalse, b) + }) + } +} diff --git a/encoder/encoder_native.go b/encoder/encoder_native.go index 3ddbe884d..c86f4ab6e 100644 --- a/encoder/encoder_native.go +++ b/encoder/encoder_native.go @@ -1,3 +1,4 @@ +//go:build (amd64 && go1.17 && !go1.24) || (arm64 && go1.20 && !go1.24) // +build amd64,go1.17,!go1.24 arm64,go1.20,!go1.24 /* @@ -19,7 +20,7 @@ package encoder import ( - `github.com/bytedance/sonic/internal/encoder` + "github.com/bytedance/sonic/internal/encoder" ) // EnableFallback indicates if encoder use fallback @@ -35,90 +36,88 @@ type StreamEncoder = encoder.StreamEncoder type Options = encoder.Options const ( - // SortMapKeys indicates that the keys of a map needs to be sorted - // before serializing into JSON. - // WARNING: This hurts performance A LOT, USE WITH CARE. - SortMapKeys Options = encoder.SortMapKeys + // SortMapKeys indicates that the keys of a map needs to be sorted + // before serializing into JSON. + // WARNING: This hurts performance A LOT, USE WITH CARE. + SortMapKeys Options = encoder.SortMapKeys - // EscapeHTML indicates encoder to escape all HTML characters - // after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape). - // WARNING: This hurts performance A LOT, USE WITH CARE. - EscapeHTML Options = encoder.EscapeHTML + // EscapeHTML indicates encoder to escape all HTML characters + // after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape). + // WARNING: This hurts performance A LOT, USE WITH CARE. + EscapeHTML Options = encoder.EscapeHTML - // CompactMarshaler indicates that the output JSON from json.Marshaler - // is always compact and needs no validation - CompactMarshaler Options = encoder.CompactMarshaler + // CompactMarshaler indicates that the output JSON from json.Marshaler + // is always compact and needs no validation + CompactMarshaler Options = encoder.CompactMarshaler - // NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler - // is always escaped string and needs no quoting - NoQuoteTextMarshaler Options = encoder.NoQuoteTextMarshaler + // NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler + // is always escaped string and needs no quoting + NoQuoteTextMarshaler Options = encoder.NoQuoteTextMarshaler - // NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}', - // instead of 'null' - NoNullSliceOrMap Options = encoder.NoNullSliceOrMap + // NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}', + // instead of 'null' + NoNullSliceOrMap Options = encoder.NoNullSliceOrMap - // ValidateString indicates that encoder should validate the input string - // before encoding it into JSON. - ValidateString Options = encoder.ValidateString + // ValidateString indicates that encoder should validate the input string + // before encoding it into JSON. + ValidateString Options = encoder.ValidateString - // NoValidateJSONMarshaler indicates that the encoder should not validate the output string - // after encoding the JSONMarshaler to JSON. - NoValidateJSONMarshaler Options = encoder.NoValidateJSONMarshaler + // NoValidateJSONMarshaler indicates that the encoder should not validate the output string + // after encoding the JSONMarshaler to JSON. + NoValidateJSONMarshaler Options = encoder.NoValidateJSONMarshaler - // NoEncoderNewline indicates that the encoder should not add a newline after every message - NoEncoderNewline Options = encoder.NoEncoderNewline + // NoEncoderNewline indicates that the encoder should not add a newline after every message + NoEncoderNewline Options = encoder.NoEncoderNewline - // CompatibleWithStd is used to be compatible with std encoder. - CompatibleWithStd Options = encoder.CompatibleWithStd + // CompatibleWithStd is used to be compatible with std encoder. + CompatibleWithStd Options = encoder.CompatibleWithStd - // Encode Infinity or Nan float into `null`, instead of returning an error. - EncodeNullForInfOrNan Options = encoder.EncodeNullForInfOrNan + // Encode Infinity or Nan float into `null`, instead of returning an error. + EncodeNullForInfOrNan Options = encoder.EncodeNullForInfOrNan - // Int64 or Uint64 into strings on Marshal - Int64ToString Options = encoder.Int64ToString - Uint64ToString Options = encoder.Uint64ToString + // Int64 or Uint64 into strings on Marshal + Integer64BitToString Options = encoder.Integer64BitToString ) - var ( - // Encode returns the JSON encoding of val, encoded with opts. - Encode = encoder.Encode - - // EncodeInto is like Encode but uses a user-supplied buffer instead of allocating a new one. - EncodeIndented = encoder.EncodeIndented - - // EncodeIndented is like Encode 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. - EncodeInto = encoder.EncodeInto - - // HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 - // characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 - // so that the JSON will be safe to embed inside HTML