From 1ba2bb0a9b73ec9b6fc8196c4f000906e03fa3a4 Mon Sep 17 00:00:00 2001 From: mleku Date: Tue, 26 Aug 2025 18:26:34 +0100 Subject: [PATCH] implement filter codec --- pkg/encoders/event/event.go | 7 +- pkg/encoders/event/event_test.go | 2 +- pkg/encoders/filter/filter.go | 424 +++ pkg/encoders/filter/filter_test.go | 102 + pkg/encoders/json/bench_test.go | 583 ---- pkg/encoders/json/decode_test.go | 2830 ---------------- pkg/encoders/json/encode_test.go | 1425 --------- pkg/encoders/json/example_marshaling_test.go | 75 - pkg/encoders/json/example_test.go | 312 -- .../json/example_text_marshaling_test.go | 69 - pkg/encoders/json/fold_test.go | 52 - pkg/encoders/json/fuzz_test.go | 85 - .../json/internal/jsonflags/flags_test.go | 75 - .../json/internal/jsonopts/options_test.go | 233 -- .../json/internal/jsonwire/decode_test.go | 443 --- .../json/internal/jsonwire/encode_test.go | 332 -- .../json/internal/jsonwire/wire_test.go | 98 - pkg/encoders/json/jsontext/coder_test.go | 856 ----- pkg/encoders/json/jsontext/decode_test.go | 1267 -------- pkg/encoders/json/jsontext/encode_test.go | 737 ----- pkg/encoders/json/jsontext/example_test.go | 130 - pkg/encoders/json/jsontext/fuzz_test.go | 236 -- pkg/encoders/json/jsontext/state_test.go | 396 --- pkg/encoders/json/jsontext/token_test.go | 168 - pkg/encoders/json/jsontext/value_test.go | 200 -- pkg/encoders/json/number_test.go | 120 - pkg/encoders/json/scanner_test.go | 306 -- pkg/encoders/json/stream_test.go | 524 --- pkg/encoders/json/tagkey_test.go | 123 - pkg/encoders/json/tags_test.go | 28 - pkg/encoders/json/v2_bench_test.go | 483 --- pkg/encoders/json/v2_decode_test.go | 2835 ----------------- pkg/encoders/json/v2_diff_test.go | 1130 ------- pkg/encoders/json/v2_encode_test.go | 1430 --------- .../json/v2_example_marshaling_test.go | 76 - pkg/encoders/json/v2_example_test.go | 313 -- .../json/v2_example_text_marshaling_test.go | 70 - pkg/encoders/json/v2_fuzz_test.go | 85 - pkg/encoders/json/v2_scanner_test.go | 306 -- pkg/encoders/json/v2_stream_test.go | 504 --- pkg/encoders/json/v2_tagkey_test.go | 121 - pkg/encoders/tag/tag.go | 14 + pkg/encoders/tag/tag_test.go | 4 +- pkg/encoders/tag/tags.go | 22 + pkg/utils/bufpool/bufpool_test.go | 71 - pkg/utils/pointers/pointers.go | 23 + pkg/utils/values/values.go | 95 + 47 files changed, 687 insertions(+), 19133 deletions(-) create mode 100644 pkg/encoders/filter/filter.go create mode 100644 pkg/encoders/filter/filter_test.go delete mode 100644 pkg/encoders/json/bench_test.go delete mode 100644 pkg/encoders/json/decode_test.go delete mode 100644 pkg/encoders/json/encode_test.go delete mode 100644 pkg/encoders/json/example_marshaling_test.go delete mode 100644 pkg/encoders/json/example_test.go delete mode 100644 pkg/encoders/json/example_text_marshaling_test.go delete mode 100644 pkg/encoders/json/fold_test.go delete mode 100644 pkg/encoders/json/fuzz_test.go delete mode 100644 pkg/encoders/json/internal/jsonflags/flags_test.go delete mode 100644 pkg/encoders/json/internal/jsonopts/options_test.go delete mode 100644 pkg/encoders/json/internal/jsonwire/decode_test.go delete mode 100644 pkg/encoders/json/internal/jsonwire/encode_test.go delete mode 100644 pkg/encoders/json/internal/jsonwire/wire_test.go delete mode 100644 pkg/encoders/json/jsontext/coder_test.go delete mode 100644 pkg/encoders/json/jsontext/decode_test.go delete mode 100644 pkg/encoders/json/jsontext/encode_test.go delete mode 100644 pkg/encoders/json/jsontext/example_test.go delete mode 100644 pkg/encoders/json/jsontext/fuzz_test.go delete mode 100644 pkg/encoders/json/jsontext/state_test.go delete mode 100644 pkg/encoders/json/jsontext/token_test.go delete mode 100644 pkg/encoders/json/jsontext/value_test.go delete mode 100644 pkg/encoders/json/number_test.go delete mode 100644 pkg/encoders/json/scanner_test.go delete mode 100644 pkg/encoders/json/stream_test.go delete mode 100644 pkg/encoders/json/tagkey_test.go delete mode 100644 pkg/encoders/json/tags_test.go delete mode 100644 pkg/encoders/json/v2_bench_test.go delete mode 100644 pkg/encoders/json/v2_decode_test.go delete mode 100644 pkg/encoders/json/v2_diff_test.go delete mode 100644 pkg/encoders/json/v2_encode_test.go delete mode 100644 pkg/encoders/json/v2_example_marshaling_test.go delete mode 100644 pkg/encoders/json/v2_example_test.go delete mode 100644 pkg/encoders/json/v2_example_text_marshaling_test.go delete mode 100644 pkg/encoders/json/v2_fuzz_test.go delete mode 100644 pkg/encoders/json/v2_scanner_test.go delete mode 100644 pkg/encoders/json/v2_stream_test.go delete mode 100644 pkg/encoders/json/v2_tagkey_test.go delete mode 100644 pkg/utils/bufpool/bufpool_test.go create mode 100644 pkg/utils/pointers/pointers.go create mode 100644 pkg/utils/values/values.go diff --git a/pkg/encoders/event/event.go b/pkg/encoders/event/event.go index cdb154c..d8300c9 100644 --- a/pkg/encoders/event/event.go +++ b/pkg/encoders/event/event.go @@ -32,7 +32,8 @@ import ( // same as go 1.25 json v1 except with this one stupidity removed. type E struct { - // ID is the SHA256 hash of the canonical encoding of the event in binary format + // ID is the SHA256 hash of the canonical encoding of the event in binary + // format ID []byte // Pubkey is the public key of the event creator in binary format @@ -57,8 +58,8 @@ type E struct { // Pubkey in binary format. Sig []byte - // b is the decode buffer for the event.E. this is where the UnmarshalJSON will - // source the memory to store all of the fields except for the tags. + // b is the decode buffer for the event.E. this is where the UnmarshalJSON + // will source the memory to store all of the fields except for the tags. b bufpool.B } diff --git a/pkg/encoders/event/event_test.go b/pkg/encoders/event/event_test.go index 8b41994..8a3571c 100644 --- a/pkg/encoders/event/event_test.go +++ b/pkg/encoders/event/event_test.go @@ -89,7 +89,7 @@ func TestExamplesCache(t *testing.T) { t.Fatalf("failed to re-marshal back original") } ev.Free() - // Don't return scanner.Bytes() to the pool as it's not a buffer we own + // Don't return scanner.Bytes() to the pool as it's a buffer from embed. // bufpool.PutBytes(b) bufpool.PutBytes(b2) bufpool.PutBytes(c) diff --git a/pkg/encoders/filter/filter.go b/pkg/encoders/filter/filter.go new file mode 100644 index 0000000..6f9427f --- /dev/null +++ b/pkg/encoders/filter/filter.go @@ -0,0 +1,424 @@ +package filter + +import ( + "bytes" + "sort" + + "lol.mleku.dev/chk" + "lol.mleku.dev/errorf" + "next.orly.dev/pkg/crypto/ec/schnorr" + "next.orly.dev/pkg/crypto/sha256" + "next.orly.dev/pkg/encoders/ints" + "next.orly.dev/pkg/encoders/kind" + "next.orly.dev/pkg/encoders/tag" + "next.orly.dev/pkg/encoders/text" + "next.orly.dev/pkg/encoders/timestamp" + "next.orly.dev/pkg/utils/pointers" +) + +// F is the primary query form for requesting events from a nostr relay. +// +// The ordering of fields of filters is not specified as in the protocol there +// is no requirement to generate a hash for fast recognition of identical +// filters. However, for internal use in a relay, by applying a consistent sort +// order, this library will produce an identical JSON from the same *set* of +// fields no matter what order they were provided. +// +// This is to facilitate the deduplication of filters so an effective identical +// match is not performed on an identical filter. +type F struct { + Ids *tag.T `json:"ids,omitempty"` + Kinds *kind.S `json:"kinds,omitempty"` + Authors *tag.T `json:"authors,omitempty"` + Tags *tag.S `json:"-,omitempty"` + Since *timestamp.T `json:"since,omitempty"` + Until *timestamp.T `json:"until,omitempty"` + Search []byte `json:"search,omitempty"` + Limit *uint `json:"limit,omitempty"` +} + +// New creates a new, reasonably initialized filter that will be ready for most uses without +// further allocations. +func New() (f *F) { + return &F{ + Ids: tag.NewWithCap(10), + Kinds: kind.NewWithCap(10), + Authors: tag.NewWithCap(10), + Tags: tag.NewSWithCap(10), + Since: timestamp.New(), + Until: timestamp.New(), + } +} + +var ( + // IDs is the JSON object key for IDs. + IDs = []byte("ids") + // Kinds is the JSON object key for Kinds. + Kinds = []byte("kinds") + // Authors is the JSON object key for Authors. + Authors = []byte("authors") + // Since is the JSON object key for Since. + Since = []byte("since") + // Until is the JSON object key for Until. + Until = []byte("until") + // Limit is the JSON object key for Limit. + Limit = []byte("limit") + // Search is the JSON object key for Search. + Search = []byte("search") +) + +// Sort the fields of a filter so a fingerprint on a filter that has the same set of content +// produces the same fingerprint. +func (f *F) Sort() { + if f.Ids != nil { + sort.Sort(f.Ids) + } + if f.Kinds != nil { + sort.Sort(f.Kinds) + } + if f.Authors != nil { + sort.Sort(f.Authors) + } + if f.Tags != nil { + for i, v := range *f.Tags { + if len(v.T) > 2 { + vv := (v.T)[1:] + sort.Slice( + vv, func(i, j int) bool { + return bytes.Compare((v.T)[i+1], (v.T)[j+1]) < 0 + }, + ) + // keep the first as is, this is the #x prefix + first := (v.T)[:1] + // append the sorted values to the prefix + v.T = append(first, vv...) + // replace the old value with the sorted one + (*f.Tags)[i] = v + } + } + sort.Sort(f.Tags) + } +} + +// Marshal a filter into raw JSON bytes, minified. The field ordering and sort +// of fields is canonicalized so that a hash can identify the same filter. +func (f *F) Marshal(dst []byte) (b []byte) { + var err error + _ = err + var first bool + // sort the fields so they come out the same + f.Sort() + // open parentheses + dst = append(dst, '{') + if f.Ids != nil && f.Ids.Len() > 0 { + first = true + dst = text.JSONKey(dst, IDs) + dst = text.MarshalHexArray(dst, f.Ids.T) + } + if f.Kinds.Len() > 0 { + if first { + dst = append(dst, ',') + } else { + first = true + } + dst = text.JSONKey(dst, Kinds) + dst = f.Kinds.Marshal(dst) + } + if f.Authors.Len() > 0 { + if first { + dst = append(dst, ',') + } else { + first = true + } + dst = text.JSONKey(dst, Authors) + dst = text.MarshalHexArray(dst, f.Authors.T) + } + if f.Tags.Len() > 0 { + // tags are stored as tags with the initial element the "#a" and the rest the list in + // each element of the tags list. eg: + // + // [["#p",""," 'z') && (tKey[0] < 'A' || tKey[0] > 'Z')) { + // key must be single alpha character + continue + } + values := tg.T[1:] + if len(values) == 0 { + continue + } + if first { + dst = append(dst, ',') + } else { + first = true + } + // append the key with # prefix + dst = append(dst, '"', '#', tKey[0], '"', ':') + dst = append(dst, '[') + for i, value := range values { + dst = append(dst, '"') + dst = append(dst, value...) + dst = append(dst, '"') + if i < len(values)-1 { + dst = append(dst, ',') + } + } + dst = append(dst, ']') + } + } + if f.Since != nil && f.Since.U64() > 0 { + if first { + dst = append(dst, ',') + } else { + first = true + } + dst = text.JSONKey(dst, Since) + dst = f.Since.Marshal(dst) + } + if f.Until != nil && f.Until.U64() > 0 { + if first { + dst = append(dst, ',') + } else { + first = true + } + dst = text.JSONKey(dst, Until) + dst = f.Until.Marshal(dst) + } + if len(f.Search) > 0 { + if first { + dst = append(dst, ',') + } else { + first = true + } + dst = text.JSONKey(dst, Search) + dst = text.AppendQuote(dst, f.Search, text.NostrEscape) + } + if pointers.Present(f.Limit) { + if first { + dst = append(dst, ',') + } else { + first = true + } + dst = text.JSONKey(dst, Limit) + dst = ints.New(*f.Limit).Marshal(dst) + } + // close parentheses + dst = append(dst, '}') + b = dst + return +} + +// Serialize a filter.F into raw minified JSON bytes. +func (f *F) Serialize() (b []byte) { return f.Marshal(nil) } + +// states of the unmarshaler +const ( + beforeOpen = iota + openParen + inKey + inKV + inVal + betweenKV + afterClose +) + +// Unmarshal a filter from raw (minified) JSON bytes into the runtime format. +// +// todo: this may tolerate whitespace, not certain currently. +func (f *F) Unmarshal(b []byte) (r []byte, err error) { + r = b[:] + var key []byte + var state int + for ; len(r) > 0; r = r[1:] { + // log.I.ToSliceOfBytes("%c", rem[0]) + switch state { + case beforeOpen: + if r[0] == '{' { + state = openParen + // log.I.Ln("openParen") + } + case openParen: + if r[0] == '"' { + state = inKey + // log.I.Ln("inKey") + } + case inKey: + if r[0] == '"' { + state = inKV + // log.I.Ln("inKV") + } else { + key = append(key, r[0]) + } + case inKV: + if r[0] == ':' { + state = inVal + } + case inVal: + if len(key) < 1 { + err = errorf.E("filter key zero length: '%s'\n'%s", b, r) + return + } + switch key[0] { + case '#': + // tags start with # and have 1 letter + l := len(key) + if l != 2 { + err = errorf.E( + "filter tag keys can only be # and one alpha character: '%s'\n%s", + key, b, + ) + return + } + k := make([]byte, len(key)) + copy(k, key) + // switch key[1] { + // case 'e', 'p': + // // the tags must all be 64 character hexadecimal + // var ff [][]byte + // if ff, r, err = text2.UnmarshalHexArray( + // r, + // sha256.Size, + // ); chk.E(err) { + // return + // } + // ff = append([][]byte{k}, ff...) + // f.Tags = f.Tags.AppendTags(tag.FromBytesSlice(ff...)) + // // f.Tags.F = append(f.Tags.F, tag.New(ff...)) + // default: + // other types of tags can be anything + var ff [][]byte + if ff, r, err = text.UnmarshalStringArray(r); chk.E(err) { + return + } + ff = append([][]byte{k}, ff...) + s := append(*f.Tags, tag.New(ff...)) + f.Tags = &s + // f.Tags.F = append(f.Tags.F, tag.New(ff...)) + // } + state = betweenKV + case IDs[0]: + if len(key) < len(IDs) { + goto invalid + } + var ff [][]byte + if ff, r, err = text.UnmarshalHexArray( + r, sha256.Size, + ); chk.E(err) { + return + } + f.Ids = tag.New(ff...) + state = betweenKV + case Kinds[0]: + if len(key) < len(Kinds) { + goto invalid + } + f.Kinds = kind.NewWithCap(0) + if r, err = f.Kinds.Unmarshal(r); chk.E(err) { + return + } + state = betweenKV + case Authors[0]: + if len(key) < len(Authors) { + goto invalid + } + var ff [][]byte + if ff, r, err = text.UnmarshalHexArray( + r, schnorr.PubKeyBytesLen, + ); chk.E(err) { + return + } + f.Authors = tag.New(ff...) + state = betweenKV + case Until[0]: + if len(key) < len(Until) { + goto invalid + } + u := ints.New(0) + if r, err = u.Unmarshal(r); chk.E(err) { + return + } + f.Until = timestamp.FromUnix(int64(u.N)) + state = betweenKV + case Limit[0]: + if len(key) < len(Limit) { + goto invalid + } + l := ints.New(0) + if r, err = l.Unmarshal(r); chk.E(err) { + return + } + u := uint(l.N) + f.Limit = &u + state = betweenKV + case Search[0]: + if len(key) < len(Since) { + goto invalid + } + switch key[1] { + case Search[1]: + if len(key) < len(Search) { + goto invalid + } + var txt []byte + if txt, r, err = text.UnmarshalQuoted(r); chk.E(err) { + return + } + f.Search = txt + // log.I.ToSliceOfBytes("\n%s\n%s", txt, rem) + state = betweenKV + // log.I.Ln("betweenKV") + case Since[1]: + if len(key) < len(Since) { + goto invalid + } + s := ints.New(0) + if r, err = s.Unmarshal(r); chk.E(err) { + return + } + f.Since = timestamp.FromUnix(int64(s.N)) + state = betweenKV + // log.I.Ln("betweenKV") + } + default: + goto invalid + } + key = key[:0] + case betweenKV: + if len(r) == 0 { + return + } + if r[0] == '}' { + state = afterClose + // log.I.Ln("afterClose") + // rem = rem[1:] + } else if r[0] == ',' { + state = openParen + // log.I.Ln("openParen") + } else if r[0] == '"' { + state = inKey + // log.I.Ln("inKey") + } + } + if len(r) == 0 { + return + } + if r[0] == '}' { + r = r[1:] + return + } + } +invalid: + err = errorf.E("invalid key,\n'%s'\n'%s'", string(b), string(r)) + return +} diff --git a/pkg/encoders/filter/filter_test.go b/pkg/encoders/filter/filter_test.go new file mode 100644 index 0000000..cc8b89c --- /dev/null +++ b/pkg/encoders/filter/filter_test.go @@ -0,0 +1,102 @@ +package filter + +import ( + "math" + "testing" + + "lukechampine.com/frand" + "next.orly.dev/pkg/crypto/ec/schnorr" + "next.orly.dev/pkg/crypto/ec/secp256k1" + "next.orly.dev/pkg/crypto/sha256" + "next.orly.dev/pkg/encoders/hex" + "next.orly.dev/pkg/encoders/kind" + "next.orly.dev/pkg/encoders/tag" + "next.orly.dev/pkg/encoders/timestamp" + "next.orly.dev/pkg/utils" + "next.orly.dev/pkg/utils/values" + + "lol.mleku.dev/chk" +) + +func TestT_MarshalUnmarshal(t *testing.T) { + var err error + const bufLen = 4000000 + dst := make([]byte, 0, bufLen) + dst1 := make([]byte, 0, bufLen) + dst2 := make([]byte, 0, bufLen) + for _ = range 20 { + f := New() + if f, err = GenFilter(); chk.E(err) { + t.Fatal(err) + } + dst = f.Marshal(dst) + dst1 = append(dst1, dst...) + // now unmarshal + var rem []byte + fa := New() + if rem, err = fa.Unmarshal(dst); chk.E(err) { + t.Fatalf("unmarshal error: %v\n%s\n%s", err, dst, rem) + } + dst2 = fa.Marshal(nil) + if !utils.FastEqual(dst1, dst2) { + t.Fatalf("marshal error: %v\n%s\n%s", err, dst1, dst2) + } + dst, dst1, dst2 = dst[:0], dst1[:0], dst2[:0] + } +} + +// GenFilter is a testing tool to create random arbitrary filters for tests. +func GenFilter() (f *F, err error) { + f = New() + n := frand.Intn(16) + for _ = range n { + id := make([]byte, sha256.Size) + frand.Read(id) + f.Ids.T = append(f.Ids.T, id) + // f.Ids.Field = append(f.Ids.Field, id) + } + n = frand.Intn(16) + for _ = range n { + f.Kinds.K = append(f.Kinds.K, kind.New(frand.Intn(math.MaxUint16))) + } + n = frand.Intn(16) + for _ = range n { + var sk *secp256k1.SecretKey + if sk, err = secp256k1.GenerateSecretKey(); chk.E(err) { + return + } + pk := sk.PubKey() + f.Authors.T = append(f.Authors.T, schnorr.SerializePubKey(pk)) + // f.Authors.Field = append(f.Authors.Field, schnorr.SerializePubKey(pk)) + } + a := frand.Intn(16) + if a < n { + n = a + } + for i := range n { + p := make([]byte, 0, schnorr.PubKeyBytesLen*2) + p = hex.EncAppend(p, f.Authors.T[i]) + } + for b := 'a'; b <= 'z'; b++ { + l := frand.Intn(6) + var idb [][]byte + for range l { + bb := make([]byte, frand.Intn(31)+1) + frand.Read(bb) + id := make([]byte, 0, len(bb)*2) + id = hex.EncAppend(id, bb) + idb = append(idb, id) + } + idb = append([][]byte{{'#', byte(b)}}, idb...) + *f.Tags = append(*f.Tags, tag.New(idb...)) + // f.Tags.F = append(f.Tags.F, tag.FromBytesSlice(idb...)) + } + tn := int(timestamp.Now().I64()) + f.Since = ×tamp.T{int64(tn - frand.Intn(10000))} + f.Until = timestamp.Now() + if frand.Intn(10) > 5 { + f.Limit = values.ToUintPointer(uint(frand.Intn(1000))) + } + f.Search = []byte("token search text") + return +} diff --git a/pkg/encoders/json/bench_test.go b/pkg/encoders/json/bench_test.go deleted file mode 100644 index 0471881..0000000 --- a/pkg/encoders/json/bench_test.go +++ /dev/null @@ -1,583 +0,0 @@ -// Copyright 2011 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. - -// Large data benchmark. -// The JSON data is a summary of agl's changes in the -// go, webkit, and chromium open source projects. -// We benchmark converting between the JSON form -// and in-memory data structures. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "bytes" - "fmt" - "internal/testenv" - "internal/zstd" - "io" - "os" - "reflect" - "regexp" - "runtime" - "strings" - "sync" - "testing" -) - -type codeResponse struct { - Tree *codeNode `json:"tree"` - Username string `json:"username"` -} - -type codeNode struct { - Name string `json:"name"` - Kids []*codeNode `json:"kids"` - CLWeight float64 `json:"cl_weight"` - Touches int `json:"touches"` - MinT int64 `json:"min_t"` - MaxT int64 `json:"max_t"` - MeanT int64 `json:"mean_t"` -} - -var codeJSON []byte -var codeStruct codeResponse - -func codeInit() { - f, err := os.Open("internal/jsontest/testdata/golang_source.json.zst") - if err != nil { - panic(err) - } - defer f.Close() - gz := zstd.NewReader(f) - data, err := io.ReadAll(gz) - if err != nil { - panic(err) - } - - codeJSON = data - - if err := Unmarshal(codeJSON, &codeStruct); err != nil { - panic("unmarshal code.json: " + err.Error()) - } - - if data, err = Marshal(&codeStruct); err != nil { - panic("marshal code.json: " + err.Error()) - } - - if !bytes.Equal(data, codeJSON) { - println("different lengths", len(data), len(codeJSON)) - for i := 0; i < len(data) && i < len(codeJSON); i++ { - if data[i] != codeJSON[i] { - println("re-marshal: changed at byte", i) - println("orig: ", string(codeJSON[i-10:i+10])) - println("new: ", string(data[i-10:i+10])) - break - } - } - panic("re-marshal code.json: different result") - } -} - -func BenchmarkCodeEncoder(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - enc := NewEncoder(io.Discard) - for pb.Next() { - if err := enc.Encode(&codeStruct); err != nil { - b.Fatalf("Encode error: %v", err) - } - } - }) - 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.Fatalf("Encode error: %v", err) - } - if _, err := Marshal(dummy); err == nil { - b.Fatal("Marshal error: got nil, want non-nil") - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkCodeMarshal(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if _, err := Marshal(&codeStruct); err != nil { - b.Fatalf("Marshal error: %v", err) - } - } - }) - 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.Fatalf("Marshal error: %v", err) - } - if _, err := Marshal(dummy); err == nil { - b.Fatal("Marshal error: got nil, want non-nil") - } - } - }) - 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 - // interface parameter to Marshal. - v := &struct { - Bytes []byte - }{ - bytes.Repeat(sample, (n/len(sample))+1)[:n], - } - return func(b *testing.B) { - for i := 0; i < b.N; i++ { - if _, err := Marshal(v); err != nil { - b.Fatalf("Marshal error: %v", err) - } - } - } -} - -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.Fatalf("Marshal error: %v", err) - } - if _, err := Marshal(dummy); err == nil { - b.Fatal("Marshal error: got nil, want non-nil") - } - } - } -} - -func BenchmarkMarshalBytes(b *testing.B) { - b.ReportAllocs() - // 32 fits within encodeState.scratch. - b.Run("32", benchMarshalBytes(32)) - // 256 doesn't fit in encodeState.scratch, but is small enough to - // allocate and avoid the slower base64.NewEncoder. - b.Run("256", benchMarshalBytes(256)) - // 4096 is large enough that we want to avoid allocating for it. - 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 BenchmarkMarshalMap(b *testing.B) { - b.ReportAllocs() - m := map[string]int{ - "key3": 3, - "key2": 2, - "key1": 1, - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if _, err := Marshal(m); err != nil { - b.Fatal("Marshal:", err) - } - } - }) -} - -func BenchmarkCodeDecoder(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - var buf bytes.Buffer - dec := NewDecoder(&buf) - var r codeResponse - for pb.Next() { - buf.Write(codeJSON) - // hide EOF - buf.WriteByte('\n') - buf.WriteByte('\n') - buf.WriteByte('\n') - if err := dec.Decode(&r); err != nil { - b.Fatalf("Decode error: %v", err) - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkUnicodeDecoder(b *testing.B) { - b.ReportAllocs() - j := []byte(`"\uD83D\uDE01"`) - b.SetBytes(int64(len(j))) - r := bytes.NewReader(j) - dec := NewDecoder(r) - var out string - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := dec.Decode(&out); err != nil { - b.Fatalf("Decode error: %v", err) - } - r.Seek(0, 0) - } -} - -func BenchmarkDecoderStream(b *testing.B) { - b.ReportAllocs() - b.StopTimer() - var buf bytes.Buffer - dec := NewDecoder(&buf) - buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") - var x any - if err := dec.Decode(&x); err != nil { - b.Fatalf("Decode error: %v", err) - } - ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" - b.StartTimer() - for i := 0; i < b.N; i++ { - if i%300000 == 0 { - buf.WriteString(ones) - } - x = nil - switch err := dec.Decode(&x); { - case err != nil: - b.Fatalf("Decode error: %v", err) - case x != 1.0: - b.Fatalf("Decode: got %v want 1.0", i) - } - } -} - -func BenchmarkCodeUnmarshal(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - var r codeResponse - if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkCodeUnmarshalReuse(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - var r codeResponse - for pb.Next() { - if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkUnmarshalString(b *testing.B) { - b.ReportAllocs() - data := []byte(`"hello, world"`) - b.RunParallel(func(pb *testing.PB) { - var s string - for pb.Next() { - if err := Unmarshal(data, &s); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmarshalFloat64(b *testing.B) { - b.ReportAllocs() - data := []byte(`3.14`) - b.RunParallel(func(pb *testing.PB) { - var f float64 - for pb.Next() { - if err := Unmarshal(data, &f); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmarshalInt64(b *testing.B) { - b.ReportAllocs() - data := []byte(`3`) - b.RunParallel(func(pb *testing.PB) { - var x int64 - for pb.Next() { - if err := Unmarshal(data, &x); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmarshalMap(b *testing.B) { - b.ReportAllocs() - data := []byte(`{"key1":"value1","key2":"value2","key3":"value3"}`) - b.RunParallel(func(pb *testing.PB) { - x := make(map[string]string, 3) - for pb.Next() { - if err := Unmarshal(data, &x); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkIssue10335(b *testing.B) { - b.ReportAllocs() - j := []byte(`{"a":{ }}`) - b.RunParallel(func(pb *testing.PB) { - var s struct{} - for pb.Next() { - if err := Unmarshal(j, &s); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkIssue34127(b *testing.B) { - b.ReportAllocs() - j := struct { - Bar string `json:"bar,string"` - }{ - Bar: `foobar`, - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if _, err := Marshal(&j); err != nil { - b.Fatalf("Marshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmapped(b *testing.B) { - b.ReportAllocs() - j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`) - b.RunParallel(func(pb *testing.PB) { - var s struct{} - for pb.Next() { - if err := Unmarshal(j, &s); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -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) - fs := []reflect.StructField{{ - Type: reflect.TypeFor[string](), - Index: []int{0}, - }} - for i := range types { - fs[0].Name = fmt.Sprintf("TypeFieldsCache%d", i) - types[i] = reflect.StructOf(fs) - } - - // clearClear clears the cache. Other JSON operations, must not be running. - clearCache := func() { - fieldCache = sync.Map{} - } - - // MissTypes tests the performance of repeated cache misses. - // This measures the time to rebuild a cache of size nt. - for nt := 1; nt <= maxTypes; nt *= 10 { - ts := types[:nt] - b.Run(fmt.Sprintf("MissTypes%d", nt), func(b *testing.B) { - nc := runtime.GOMAXPROCS(0) - for i := 0; i < b.N; i++ { - clearCache() - var wg sync.WaitGroup - for j := 0; j < nc; j++ { - wg.Add(1) - go func(j int) { - for _, t := range ts[(j*len(ts))/nc : ((j+1)*len(ts))/nc] { - cachedTypeFields(t) - } - wg.Done() - }(j) - } - wg.Wait() - } - }) - } - - // HitTypes tests the performance of repeated cache hits. - // This measures the average time of each cache lookup. - for nt := 1; nt <= maxTypes; nt *= 10 { - // Pre-warm a cache of size nt. - clearCache() - for _, t := range types[:nt] { - cachedTypeFields(t) - } - b.Run(fmt.Sprintf("HitTypes%d", nt), func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - cachedTypeFields(types[0]) - } - }) - }) - } -} - -func BenchmarkEncodeMarshaler(b *testing.B) { - b.ReportAllocs() - - m := struct { - A int - B RawMessage - }{} - - b.RunParallel(func(pb *testing.PB) { - enc := NewEncoder(io.Discard) - - for pb.Next() { - if err := enc.Encode(&m); err != nil { - b.Fatalf("Encode error: %v", err) - } - } - }) -} - -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.Fatalf("Encode error: %v", 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) - } -} - -func BenchmarkUnmarshalNumber(b *testing.B) { - b.ReportAllocs() - data := []byte(`"-61657.61667E+61673"`) - var number Number - for i := 0; i < b.N; i++ { - if err := Unmarshal(data, &number); err != nil { - b.Fatal("Unmarshal:", err) - } - } -} diff --git a/pkg/encoders/json/decode_test.go b/pkg/encoders/json/decode_test.go deleted file mode 100644 index 0df31c8..0000000 --- a/pkg/encoders/json/decode_test.go +++ /dev/null @@ -1,2830 +0,0 @@ -// Copyright 2010 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. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "image" - "io" - "maps" - "math" - "math/big" - "net" - "reflect" - "slices" - "strconv" - "strings" - "testing" - "time" -) - -type T struct { - X string - Y int - Z int `json:"-"` -} - -type U struct { - Alphabet string `json:"alpha"` -} - -type V struct { - F1 any - F2 int32 - F3 Number - F4 *VOuter -} - -type VOuter struct { - V V -} - -type W struct { - S SS -} - -type P struct { - PP PP -} - -type PP struct { - T T - Ts []T -} - -type SS string - -func (*SS) UnmarshalJSON(data []byte) error { - return &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[SS]()} -} - -type TAlias T - -func (tt *TAlias) UnmarshalJSON(data []byte) error { - t := T{} - if err := Unmarshal(data, &t); err != nil { - return err - } - *tt = TAlias(t) - return nil -} - -type TOuter struct { - T TAlias -} - -// ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and -// without UseNumber -var ifaceNumAsFloat64 = map[string]any{ - "k1": float64(1), - "k2": "s", - "k3": []any{float64(1), float64(2.0), float64(3e-3)}, - "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, -} - -var ifaceNumAsNumber = map[string]any{ - "k1": Number("1"), - "k2": "s", - "k3": []any{Number("1"), Number("2.0"), Number("3e-3")}, - "k4": map[string]any{"kk1": "s", "kk2": Number("2")}, -} - -type tx struct { - x int -} - -type u8 uint8 - -// A type that can unmarshal itself. - -type unmarshaler struct { - T bool -} - -func (u *unmarshaler) UnmarshalJSON(b []byte) error { - *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. - return nil -} - -type ustruct struct { - M unmarshaler -} - -type unmarshalerText struct { - A, B string -} - -// needed for re-marshaling tests -func (u unmarshalerText) MarshalText() ([]byte, error) { - return []byte(u.A + ":" + u.B), nil -} - -func (u *unmarshalerText) UnmarshalText(b []byte) error { - pos := bytes.IndexByte(b, ':') - if pos == -1 { - return errors.New("missing separator") - } - u.A, u.B = string(b[:pos]), string(b[pos+1:]) - return nil -} - -var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) - -type ustructText struct { - M unmarshalerText -} - -// u8marshal is an integer type that can marshal/unmarshal itself. -type u8marshal uint8 - -func (u8 u8marshal) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("u%d", u8)), nil -} - -var errMissingU8Prefix = errors.New("missing 'u' prefix") - -func (u8 *u8marshal) UnmarshalText(b []byte) error { - if !bytes.HasPrefix(b, []byte{'u'}) { - return errMissingU8Prefix - } - n, err := strconv.Atoi(string(b[1:])) - if err != nil { - return err - } - *u8 = u8marshal(n) - return nil -} - -var _ encoding.TextUnmarshaler = (*u8marshal)(nil) - -var ( - umtrue = unmarshaler{true} - umslice = []unmarshaler{{true}} - umstruct = ustruct{unmarshaler{true}} - - umtrueXY = unmarshalerText{"x", "y"} - umsliceXY = []unmarshalerText{{"x", "y"}} - umstructXY = ustructText{unmarshalerText{"x", "y"}} - - ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} -) - -// Test data structures for anonymous fields. - -type Point struct { - Z int -} - -type Top struct { - Level0 int - Embed0 - *Embed0a - *Embed0b `json:"e,omitempty"` // treated as named - Embed0c `json:"-"` // ignored - Loop - Embed0p // has Point with X, Y, used - Embed0q // has Point with Z, used - embed // contains exported field -} - -type Embed0 struct { - Level1a int // overridden by Embed0a's Level1a with json tag - Level1b int // used because Embed0a's Level1b is renamed - Level1c int // used because Embed0a's Level1c is ignored - Level1d int // annihilated by Embed0a's Level1d - Level1e int `json:"x"` // annihilated by Embed0a.Level1e -} - -type Embed0a struct { - Level1a int `json:"Level1a,omitempty"` - Level1b int `json:"LEVEL1B,omitempty"` - Level1c int `json:"-"` - Level1d int // annihilated by Embed0's Level1d - Level1f int `json:"x"` // annihilated by Embed0's Level1e -} - -type Embed0b Embed0 - -type Embed0c Embed0 - -type Embed0p struct { - image.Point -} - -type Embed0q struct { - Point -} - -type embed struct { - Q int -} - -type Loop struct { - Loop1 int `json:",omitempty"` - Loop2 int `json:",omitempty"` - *Loop -} - -// From reflect test: -// The X in S6 and S7 annihilate, but they also block the X in S8.S9. -type S5 struct { - S6 - S7 - S8 -} - -type S6 struct { - X int -} - -type S7 S6 - -type S8 struct { - S9 -} - -type S9 struct { - X int - Y int -} - -// From reflect test: -// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. -type S10 struct { - S11 - S12 - S13 -} - -type S11 struct { - S6 -} - -type S12 struct { - S6 -} - -type S13 struct { - S8 -} - -type Ambig struct { - // Given "hello", the first match should win. - First int `json:"HELLO"` - Second int `json:"Hello"` -} - -type XYZ struct { - X any - Y any - Z any -} - -type unexportedWithMethods struct{} - -func (unexportedWithMethods) F() {} - -type byteWithMarshalJSON byte - -func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil -} - -func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalJSON(i) - return nil -} - -type byteWithPtrMarshalJSON byte - -func (b *byteWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return byteWithMarshalJSON(*b).MarshalJSON() -} - -func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type byteWithMarshalText byte - -func (b byteWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil -} - -func (b *byteWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalText(i) - return nil -} - -type byteWithPtrMarshalText byte - -func (b *byteWithPtrMarshalText) MarshalText() ([]byte, error) { - return byteWithMarshalText(*b).MarshalText() -} - -func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*byteWithMarshalText)(b).UnmarshalText(data) -} - -type intWithMarshalJSON int - -func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil -} - -func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalJSON(i) - return nil -} - -type intWithPtrMarshalJSON int - -func (b *intWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return intWithMarshalJSON(*b).MarshalJSON() -} - -func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*intWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type intWithMarshalText int - -func (b intWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil -} - -func (b *intWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalText(i) - return nil -} - -type intWithPtrMarshalText int - -func (b *intWithPtrMarshalText) MarshalText() ([]byte, error) { - return intWithMarshalText(*b).MarshalText() -} - -func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*intWithMarshalText)(b).UnmarshalText(data) -} - -type mapStringToStringData struct { - Data map[string]string `json:"data"` -} - -type B struct { - B bool `json:",string"` -} - -type DoublePtr struct { - I **int - J **int -} - -var unmarshalTests = []struct { - CaseName - in string - ptr any // new(type) - out any - err error - useNumber bool - golden bool - disallowUnknownFields bool -}{ - // basic types - {CaseName: Name(""), in: `true`, ptr: new(bool), out: true}, - {CaseName: Name(""), in: `1`, ptr: new(int), out: 1}, - {CaseName: Name(""), in: `1.2`, ptr: new(float64), out: 1.2}, - {CaseName: Name(""), in: `-5`, ptr: new(int16), out: int16(-5)}, - {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, - {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2")}, - {CaseName: Name(""), in: `2`, ptr: new(any), out: float64(2.0)}, - {CaseName: Name(""), in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, - {CaseName: Name(""), in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, - {CaseName: Name(""), in: `"http:\/\/"`, ptr: new(string), out: "http://"}, - {CaseName: Name(""), in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, - {CaseName: Name(""), in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, - {CaseName: Name(""), in: "null", ptr: new(any), out: nil}, - {CaseName: Name(""), in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), 7, "T", "X"}}, - {CaseName: Name(""), in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 8, "T", "X"}}, - {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, - {CaseName: Name(""), in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[SS](), 0, "W", "S"}}, - {CaseName: Name(""), in: `{"T": {"X": 23}}`, ptr: new(TOuter), out: TOuter{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), 8, "TOuter", "T.X"}}, - {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, - {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, - {CaseName: Name(""), 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 - {CaseName: Name(""), in: "\n true ", ptr: new(bool), out: true}, - {CaseName: Name(""), in: "\t 1 ", ptr: new(int), out: 1}, - {CaseName: Name(""), in: "\r 1.2 ", ptr: new(float64), out: 1.2}, - {CaseName: Name(""), in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, - {CaseName: Name(""), in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, - - // Z has a "-" tag. - {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, - {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}, err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, - - {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}, err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - {CaseName: Name(""), in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, - {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - - // syntax errors - {CaseName: Name(""), in: ``, ptr: new(any), err: &SyntaxError{"unexpected end of JSON input", 0}}, - {CaseName: Name(""), in: " \n\r\t", ptr: new(any), err: &SyntaxError{"unexpected end of JSON input", 4}}, - {CaseName: Name(""), in: `[2, 3`, ptr: new(any), err: &SyntaxError{"unexpected end of JSON input", 5}}, - {CaseName: Name(""), in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, - {CaseName: Name(""), in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}}, - {CaseName: Name(""), in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true}, - {CaseName: Name(""), in: `{"F3": -}`, ptr: new(V), err: &SyntaxError{"invalid character '}' in numeric literal", 9}}, - - // raw value errors - {CaseName: Name(""), in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {CaseName: Name(""), in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 5}}, - {CaseName: Name(""), in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {CaseName: Name(""), in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 8}}, - {CaseName: Name(""), in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {CaseName: Name(""), in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 6}}, - {CaseName: Name(""), in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {CaseName: Name(""), in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 11}}, - - // array tests - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, - - // empty array to interface test - {CaseName: Name(""), in: `[]`, ptr: new([]any), out: []any{}}, - {CaseName: Name(""), in: `null`, ptr: new([]any), out: []any(nil)}, - {CaseName: Name(""), in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, - {CaseName: Name(""), in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, - - // composite tests - {CaseName: Name(""), in: allValueIndent, ptr: new(All), out: allValue}, - {CaseName: Name(""), in: allValueCompact, ptr: new(All), out: allValue}, - {CaseName: Name(""), in: allValueIndent, ptr: new(*All), out: &allValue}, - {CaseName: Name(""), in: allValueCompact, ptr: new(*All), out: &allValue}, - {CaseName: Name(""), in: pallValueIndent, ptr: new(All), out: pallValue}, - {CaseName: Name(""), in: pallValueCompact, ptr: new(All), out: pallValue}, - {CaseName: Name(""), in: pallValueIndent, ptr: new(*All), out: &pallValue}, - {CaseName: Name(""), in: pallValueCompact, ptr: new(*All), out: &pallValue}, - - // unmarshal interface test - {CaseName: Name(""), in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called - {CaseName: Name(""), in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, - {CaseName: Name(""), in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, - {CaseName: Name(""), in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, - {CaseName: Name(""), in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, - - // UnmarshalText interface test - {CaseName: Name(""), in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, - {CaseName: Name(""), in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, - {CaseName: Name(""), in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, - {CaseName: Name(""), in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, - {CaseName: Name(""), in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, - - // integer-keyed map test - { - CaseName: Name(""), - in: `{"-1":"a","0":"b","1":"c"}`, - ptr: new(map[int]string), - out: map[int]string{-1: "a", 0: "b", 1: "c"}, - }, - { - CaseName: Name(""), - in: `{"0":"a","10":"c","9":"b"}`, - ptr: new(map[u8]string), - out: map[u8]string{0: "a", 9: "b", 10: "c"}, - }, - { - CaseName: Name(""), - in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, - ptr: new(map[int64]string), - out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, - }, - { - CaseName: Name(""), - in: `{"18446744073709551615":"max"}`, - ptr: new(map[uint64]string), - out: map[uint64]string{math.MaxUint64: "max"}, - }, - { - CaseName: Name(""), - in: `{"0":false,"10":true}`, - ptr: new(map[uintptr]bool), - out: map[uintptr]bool{0: false, 10: true}, - }, - - // Check that MarshalText and UnmarshalText take precedence - // over default integer handling in map keys. - { - CaseName: Name(""), - in: `{"u2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{2: 4}, - }, - { - CaseName: Name(""), - in: `{"2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{}, - err: errMissingU8Prefix, - }, - - // integer-keyed map errors - { - CaseName: Name(""), - in: `{"abc":"abc"}`, - ptr: new(map[int]string), - out: map[int]string{}, - err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeFor[int](), Offset: 2}, - }, - { - CaseName: Name(""), - in: `{"256":"abc"}`, - ptr: new(map[uint8]string), - out: map[uint8]string{}, - err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeFor[uint8](), Offset: 2}, - }, - { - CaseName: Name(""), - in: `{"128":"abc"}`, - ptr: new(map[int8]string), - out: map[int8]string{}, - err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeFor[int8](), Offset: 2}, - }, - { - CaseName: Name(""), - in: `{"-1":"abc"}`, - ptr: new(map[uint8]string), - out: map[uint8]string{}, - err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeFor[uint8](), Offset: 2}, - }, - { - CaseName: Name(""), - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[int]int), - out: map[string]map[int]int{"F": {3: 4}}, - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[int](), Offset: 7}, - }, - { - CaseName: Name(""), - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[uint]int), - out: map[string]map[uint]int{"F": {3: 4}}, - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[uint](), Offset: 7}, - }, - - // Map keys can be encoding.TextUnmarshalers. - {CaseName: Name(""), in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - // If multiple values for the same key exists, only the most recent value is used. - {CaseName: Name(""), in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - - { - CaseName: Name(""), - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - }, - }, - { - CaseName: Name(""), - in: `{"hello": 1}`, - ptr: new(Ambig), - out: Ambig{First: 1}, - }, - - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9: S9{Y: 2}}}, - }, - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9{Y: 2}}}, - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, - }, - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8{S9{Y: 2}}}}, - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - CaseName: Name(""), - in: `{"I": 0, "I": null, "J": null}`, - ptr: new(DoublePtr), - out: DoublePtr{I: nil, J: nil}, - }, - - // invalid UTF-8 is coerced to valid UTF-8. - { - CaseName: Name(""), - in: "\"hello\xffworld\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\xc2\xc2world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\xc2\xffworld\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\\ud800world\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", - ptr: new(string), - out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", - }, - - // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. - { - CaseName: Name(""), - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[time.Time]string), - out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, - }, - - // issue 8305 - { - CaseName: Name(""), - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[Point]string), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[Point]string](), Offset: 1}, - }, - { - CaseName: Name(""), - in: `{"asdf": "hello world"}`, - ptr: new(map[unmarshaler]string), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[unmarshaler]string](), Offset: 1}, - }, - - // related to issue 13783. - // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, - // similar to marshaling a slice of typed int. - // These tests check that, assuming the byte type also has valid decoding methods, - // either the old base64 string encoding or the new per-element encoding can be - // successfully unmarshaled. The custom unmarshalers were accessible in earlier - // versions of Go, even though the custom marshaler was not. - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - golden: true, - }, - - // ints work with the marshaler but not the base64 []byte case - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalJSON), - out: []intWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalText), - out: []intWithMarshalText{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalJSON), - out: []intWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalText), - out: []intWithPtrMarshalText{1, 2, 3}, - golden: true, - }, - - {CaseName: Name(""), in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, - {CaseName: Name(""), in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, - {CaseName: Name(""), in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, - {CaseName: Name(""), in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, - {CaseName: Name(""), in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, - {CaseName: Name(""), in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, - {CaseName: Name(""), in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, - {CaseName: Name(""), in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, - {CaseName: Name(""), in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, - {CaseName: Name(""), in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, - {CaseName: Name(""), in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, - - { - CaseName: Name(""), - in: `{"V": {"F2": "hello"}}`, - ptr: new(VOuter), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "V.F2", - Type: reflect.TypeFor[int32](), - Offset: 20, - }, - }, - { - CaseName: Name(""), - in: `{"V": {"F4": {}, "F2": "hello"}}`, - ptr: new(VOuter), - out: VOuter{V: V{F4: &VOuter{}}}, - err: &UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "V.F2", - Type: reflect.TypeFor[int32](), - Offset: 30, - }, - }, - - { - CaseName: Name(""), - in: `{"Level1a": "hello"}`, - ptr: new(Top), - out: Top{Embed0a: &Embed0a{}}, - err: &UnmarshalTypeError{ - Value: "string", - Struct: "Top", - Field: "Embed0a.Level1a", - Type: reflect.TypeFor[int](), - Offset: 19, - }, - }, - - // issue 15146. - // invalid inputs in wrongStringTests below. - {CaseName: Name(""), in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, - {CaseName: Name(""), in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, - {CaseName: Name(""), in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, - {CaseName: Name(""), in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, - {CaseName: Name(""), in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, - {CaseName: Name(""), in: `{"B": "null"}`, ptr: new(B), out: B{false}}, - {CaseName: Name(""), in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, - {CaseName: Name(""), in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, - - // additional tests for disallowUnknownFields - { - CaseName: Name(""), - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18, - "extra": true - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{Level1a: 5, Level1b: 6}, - Embed0b: &Embed0b{Level1a: 8, Level1b: 9, Level1c: 10, Level1d: 11, Level1e: 12}, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - Loop: nil, - }, - Embed0p: Embed0p{ - Point: image.Point{ - X: 15, - Y: 16, - }, - }, - Embed0q: Embed0q{Point: Point{Z: 17}}, - embed: embed{Q: 18}, - }, - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - { - CaseName: Name(""), - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12, - "extra": null - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{Level1a: 5, Level1b: 6}, - Embed0b: &Embed0b{Level1a: 8, Level1b: 9, Level1c: 10, Level1d: 11, Level1e: 12}, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - Loop: nil, - }, - Embed0p: Embed0p{ - Point: image.Point{ - X: 15, - Y: 16, - }, - }, - Embed0q: Embed0q{Point: Point{Z: 17}}, - embed: embed{Q: 18}, - }, - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - // issue 26444 - // UnmarshalTypeError without field & struct values - { - CaseName: Name(""), - in: `{"data":{"test1": "bob", "test2": 123}}`, - ptr: new(mapStringToStringData), - out: mapStringToStringData{map[string]string{"test1": "bob", "test2": ""}}, - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, - }, - { - CaseName: Name(""), - in: `{"data":{"test1": 123, "test2": "bob"}}`, - ptr: new(mapStringToStringData), - out: mapStringToStringData{Data: map[string]string{"test1": "", "test2": "bob"}}, - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, - }, - - // trying to decode JSON arrays or objects via TextUnmarshaler - { - CaseName: Name(""), - in: `[1, 2, 3]`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeFor[*MustNotUnmarshalText](), Offset: 1}, - }, - { - CaseName: Name(""), - in: `{"foo": "bar"}`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[*MustNotUnmarshalText](), Offset: 1}, - }, - // #22369 - { - CaseName: Name(""), - in: `{"PP": {"T": {"Y": "bad-type"}}}`, - ptr: new(P), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "T", - Field: "PP.T.Y", - Type: reflect.TypeFor[int](), - Offset: 29, - }, - }, - { - CaseName: Name(""), - in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, - ptr: new(PP), - out: PP{Ts: []T{{Y: 1}, {Y: 2}, {Y: 0}}}, - err: &UnmarshalTypeError{ - Value: "string", - Struct: "T", - Field: "Ts.Y", - Type: reflect.TypeFor[int](), - Offset: 44, - }, - }, - // #14702 - { - CaseName: Name(""), - in: `invalid`, - ptr: new(Number), - err: &SyntaxError{ - msg: "invalid character 'i' looking for beginning of value", - Offset: 1, - }, - }, - { - CaseName: Name(""), - in: `"invalid"`, - ptr: new(Number), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, - { - CaseName: Name(""), - in: `{"A":"invalid"}`, - ptr: new(struct{ A Number }), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, - { - CaseName: Name(""), - in: `{"A":"invalid"}`, - ptr: new(struct { - A Number `json:",string"` - }), - err: fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into json.Number", `invalid`), - }, - { - CaseName: Name(""), - in: `{"A":"invalid"}`, - ptr: new(map[string]Number), - out: map[string]Number{}, - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, - - { - CaseName: Name(""), - in: `5`, - ptr: new(Number), - out: Number("5"), - }, - { - CaseName: Name(""), - in: `"5"`, - ptr: new(Number), - out: Number("5"), - }, - { - CaseName: Name(""), - in: `{"N":5}`, - ptr: new(struct{ N Number }), - out: struct{ N Number }{"5"}, - }, - { - CaseName: Name(""), - in: `{"N":"5"}`, - ptr: new(struct{ N Number }), - out: struct{ N Number }{"5"}, - }, - { - CaseName: Name(""), - in: `{"N":5}`, - ptr: new(struct { - N Number `json:",string"` - }), - err: fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into json.Number"), - }, - { - CaseName: Name(""), - in: `{"N":"5"}`, - ptr: new(struct { - N Number `json:",string"` - }), - out: struct { - N Number `json:",string"` - }{"5"}, - }, - - // Verify that syntactic errors are immediately fatal, - // while semantic errors are lazily reported - // (i.e., allow processing to continue). - { - CaseName: Name(""), - in: `[1,2,true,4,5}`, - ptr: new([]int), - err: &SyntaxError{msg: "invalid character '}' after array element", Offset: 14}, - }, - { - CaseName: Name(""), - in: `[1,2,true,4,5]`, - ptr: new([]int), - out: []int{1, 2, 0, 4, 5}, - err: &UnmarshalTypeError{Value: "bool", Type: reflect.TypeFor[int](), Offset: 9}, - }, - - { - CaseName: Name("DashComma"), - in: `{"-":"hello"}`, - ptr: new(struct { - F string `json:"-,"` - }), - out: struct { - F string `json:"-,"` - }{"hello"}, - }, - { - CaseName: Name("DashCommaOmitEmpty"), - in: `{"-":"hello"}`, - ptr: new(struct { - F string `json:"-,omitempty"` - }), - out: struct { - F string `json:"-,omitempty"` - }{"hello"}, - }, -} - -func TestMarshal(t *testing.T) { - b, err := Marshal(allValue) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(b) != allValueCompact { - t.Errorf("Marshal:") - diff(t, b, []byte(allValueCompact)) - return - } - - b, err = Marshal(pallValue) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(b) != pallValueCompact { - t.Errorf("Marshal:") - diff(t, b, []byte(pallValueCompact)) - return - } -} - -func TestMarshalInvalidUTF8(t *testing.T) { - tests := []struct { - CaseName - in string - want string - }{ - {Name(""), "hello\xffworld", `"hello\ufffdworld"`}, - {Name(""), "", `""`}, - {Name(""), "\xff", `"\ufffd"`}, - {Name(""), "\xff\xff", `"\ufffd\ufffd"`}, - {Name(""), "a\xffb", `"a\ufffdb"`}, - {Name(""), "\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - got, err := Marshal(tt.in) - if string(got) != tt.want || err != nil { - t.Errorf("%s: Marshal(%q):\n\tgot: (%q, %v)\n\twant: (%q, nil)", tt.Where, tt.in, got, err, tt.want) - } - }) - } -} - -func TestMarshalNumberZeroVal(t *testing.T) { - var n Number - out, err := Marshal(n) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - got := string(out) - if got != "0" { - t.Fatalf("Marshal: got %s, want 0", got) - } -} - -func TestMarshalEmbeds(t *testing.T) { - top := &Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - } - got, err := Marshal(top) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func equalError(a, b error) bool { - isJSONError := func(err error) bool { - switch err.(type) { - case - *InvalidUTF8Error, - *InvalidUnmarshalError, - *MarshalerError, - *SyntaxError, - *UnmarshalFieldError, - *UnmarshalTypeError, - *UnsupportedTypeError, - *UnsupportedValueError: - return true - } - return false - } - - if a == nil || b == nil { - return a == nil && b == nil - } - if isJSONError(a) || isJSONError(b) { - return reflect.DeepEqual(a, b) // safe for locally defined error types - } - return a.Error() == b.Error() -} - -func TestUnmarshal(t *testing.T) { - for _, tt := range unmarshalTests { - t.Run(tt.Name, func(t *testing.T) { - in := []byte(tt.in) - var scan scanner - if err := checkValid(in, &scan); err != nil { - if !equalError(err, tt.err) { - t.Fatalf("%s: checkValid error:\n\tgot %#v\n\twant %#v", tt.Where, err, tt.err) - } - } - if tt.ptr == nil { - return - } - - typ := reflect.TypeOf(tt.ptr) - if typ.Kind() != reflect.Pointer { - t.Fatalf("%s: unmarshalTest.ptr %T is not a pointer type", tt.Where, tt.ptr) - } - typ = typ.Elem() - - // v = new(right-type) - v := reflect.New(typ) - - if !reflect.DeepEqual(tt.ptr, v.Interface()) { - // There's no reason for ptr to point to non-zero data, - // as we decode into new(right-type), so the data is - // discarded. - // This can easily mean tests that silently don't test - // what they should. To test decoding into existing - // data, see TestPrefilled. - t.Fatalf("%s: unmarshalTest.ptr %#v is not a pointer to a zero value", tt.Where, tt.ptr) - } - - dec := NewDecoder(bytes.NewReader(in)) - if tt.useNumber { - dec.UseNumber() - } - if tt.disallowUnknownFields { - dec.DisallowUnknownFields() - } - if tt.err != nil && strings.Contains(tt.err.Error(), "unexpected end of JSON input") { - // In streaming mode, we expect EOF or ErrUnexpectedEOF instead. - if strings.TrimSpace(tt.in) == "" { - tt.err = io.EOF - } else { - tt.err = io.ErrUnexpectedEOF - } - } - if err := dec.Decode(v.Interface()); !equalError(err, tt.err) { - t.Fatalf("%s: Decode error:\n\tgot: %v\n\twant: %v\n\n\tgot: %#v\n\twant: %#v", tt.Where, err, tt.err, err, tt.err) - } else if err != nil && tt.out == nil { - // Initialize tt.out during an error where there are no mutations, - // so the output is just the zero value of the input type. - tt.out = reflect.Zero(v.Elem().Type()).Interface() - } - if got := v.Elem().Interface(); !reflect.DeepEqual(got, tt.out) { - gotJSON, _ := Marshal(got) - wantJSON, _ := Marshal(tt.out) - t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", tt.Where, got, tt.out, gotJSON, wantJSON) - } - - // Check round trip also decodes correctly. - if tt.err == nil { - enc, err := Marshal(v.Interface()) - if err != nil { - t.Fatalf("%s: Marshal error after roundtrip: %v", tt.Where, err) - } - if tt.golden && !bytes.Equal(enc, in) { - t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, enc, in) - } - vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = NewDecoder(bytes.NewReader(enc)) - if tt.useNumber { - dec.UseNumber() - } - if err := dec.Decode(vv.Interface()); err != nil { - t.Fatalf("%s: Decode(%#q) error after roundtrip: %v", tt.Where, enc, err) - } - if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { - t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", - tt.Where, v.Elem().Interface(), vv.Elem().Interface(), - stripWhitespace(string(enc)), stripWhitespace(string(in))) - } - } - }) - } -} - -func TestUnmarshalMarshal(t *testing.T) { - initBig() - var v any - if err := Unmarshal(jsonBig, &v); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - b, err := Marshal(v) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if !bytes.Equal(jsonBig, b) { - t.Errorf("Marshal:") - diff(t, b, jsonBig) - return - } -} - -// Independent of Decode, basic coverage of the accessors in Number -func TestNumberAccessors(t *testing.T) { - tests := []struct { - CaseName - in string - i int64 - intErr string - f float64 - floatErr string - }{ - {CaseName: Name(""), in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, - {CaseName: Name(""), in: "-12", i: -12, f: -12.0}, - {CaseName: Name(""), in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - n := Number(tt.in) - if got := n.String(); got != tt.in { - t.Errorf("%s: Number(%q).String() = %s, want %s", tt.Where, tt.in, got, tt.in) - } - if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { - t.Errorf("%s: Number(%q).Int64() = %d, want %d", tt.Where, tt.in, i, tt.i) - } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { - t.Errorf("%s: Number(%q).Int64() error:\n\tgot: %v\n\twant: %v", tt.Where, tt.in, err, tt.intErr) - } - if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { - t.Errorf("%s: Number(%q).Float64() = %g, want %g", tt.Where, tt.in, f, tt.f) - } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { - t.Errorf("%s: Number(%q).Float64() error:\n\tgot %v\n\twant: %v", tt.Where, tt.in, err, tt.floatErr) - } - }) - } -} - -func TestLargeByteSlice(t *testing.T) { - s0 := make([]byte, 2000) - for i := range s0 { - s0[i] = byte(i) - } - b, err := Marshal(s0) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var s1 []byte - if err := Unmarshal(b, &s1); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !bytes.Equal(s0, s1) { - t.Errorf("Marshal:") - diff(t, s0, s1) - } -} - -type Xint struct { - X int -} - -func TestUnmarshalInterface(t *testing.T) { - var xint Xint - var i any = &xint - if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if xint.X != 1 { - t.Fatalf("xint.X = %d, want 1", xint.X) - } -} - -func TestUnmarshalPtrPtr(t *testing.T) { - var xint Xint - pxint := &xint - if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if xint.X != 1 { - t.Fatalf("xint.X = %d, want 1", xint.X) - } -} - -func TestEscape(t *testing.T) { - const input = `"foobar"` + " [\u2028 \u2029]" - const want = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` - got, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Errorf("Marshal(%#q):\n\tgot: %s\n\twant: %s", input, got, want) - } -} - -// If people misuse the ,string modifier, the error message should be -// helpful, telling the user that they're doing it wrong. -func TestErrorMessageFromMisusedString(t *testing.T) { - // WrongString is a struct that's misusing the ,string modifier. - type WrongString struct { - Message string `json:"result,string"` - } - tests := []struct { - CaseName - in, err string - }{ - {Name(""), `{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, - {Name(""), `{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, - {Name(""), `{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, - {Name(""), `{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, - {Name(""), `{"result":"\""}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"" into string`}, - {Name(""), `{"result":"\"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"foo" into string`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - r := strings.NewReader(tt.in) - var s WrongString - err := NewDecoder(r).Decode(&s) - got := fmt.Sprintf("%v", err) - if got != tt.err { - t.Errorf("%s: Decode error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.err) - } - }) - } -} - -type All struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Uintptr uintptr - Float32 float32 - Float64 float64 - - Foo string `json:"bar"` - Foo2 string `json:"bar2,dummyopt"` - - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - - PBool *bool - PInt *int - PInt8 *int8 - PInt16 *int16 - PInt32 *int32 - PInt64 *int64 - PUint *uint - PUint8 *uint8 - PUint16 *uint16 - PUint32 *uint32 - PUint64 *uint64 - PUintptr *uintptr - PFloat32 *float32 - PFloat64 *float64 - - String string - PString *string - - Map map[string]Small - MapP map[string]*Small - PMap *map[string]Small - PMapP *map[string]*Small - - EmptyMap map[string]Small - NilMap map[string]Small - - Slice []Small - SliceP []*Small - PSlice *[]Small - PSliceP *[]*Small - - EmptySlice []Small - NilSlice []Small - - StringSlice []string - ByteSlice []byte - - Small Small - PSmall *Small - PPSmall **Small - - Interface any - PInterface *any - - unexported int -} - -type Small struct { - Tag string -} - -var allValue = All{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Uintptr: 12, - Float32: 14.1, - Float64: 15.1, - Foo: "foo", - Foo2: "foo2", - IntStr: 42, - UintptrStr: 44, - String: "16", - Map: map[string]Small{ - "17": {Tag: "tag17"}, - "18": {Tag: "tag18"}, - }, - MapP: map[string]*Small{ - "19": {Tag: "tag19"}, - "20": nil, - }, - EmptyMap: map[string]Small{}, - Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, - SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, - EmptySlice: []Small{}, - StringSlice: []string{"str24", "str25", "str26"}, - ByteSlice: []byte{27, 28, 29}, - Small: Small{Tag: "tag30"}, - PSmall: &Small{Tag: "tag31"}, - Interface: 5.2, -} - -var pallValue = All{ - PBool: &allValue.Bool, - PInt: &allValue.Int, - PInt8: &allValue.Int8, - PInt16: &allValue.Int16, - PInt32: &allValue.Int32, - PInt64: &allValue.Int64, - PUint: &allValue.Uint, - PUint8: &allValue.Uint8, - PUint16: &allValue.Uint16, - PUint32: &allValue.Uint32, - PUint64: &allValue.Uint64, - PUintptr: &allValue.Uintptr, - PFloat32: &allValue.Float32, - PFloat64: &allValue.Float64, - PString: &allValue.String, - PMap: &allValue.Map, - PMapP: &allValue.MapP, - PSlice: &allValue.Slice, - PSliceP: &allValue.SliceP, - PPSmall: &allValue.PSmall, - PInterface: &allValue.Interface, -} - -var allValueIndent = `{ - "Bool": true, - "Int": 2, - "Int8": 3, - "Int16": 4, - "Int32": 5, - "Int64": 6, - "Uint": 7, - "Uint8": 8, - "Uint16": 9, - "Uint32": 10, - "Uint64": 11, - "Uintptr": 12, - "Float32": 14.1, - "Float64": 15.1, - "bar": "foo", - "bar2": "foo2", - "IntStr": "42", - "UintptrStr": "44", - "PBool": null, - "PInt": null, - "PInt8": null, - "PInt16": null, - "PInt32": null, - "PInt64": null, - "PUint": null, - "PUint8": null, - "PUint16": null, - "PUint32": null, - "PUint64": null, - "PUintptr": null, - "PFloat32": null, - "PFloat64": null, - "String": "16", - "PString": null, - "Map": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "MapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "PMap": null, - "PMapP": null, - "EmptyMap": {}, - "NilMap": null, - "Slice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "SliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "PSlice": null, - "PSliceP": null, - "EmptySlice": [], - "NilSlice": null, - "StringSlice": [ - "str24", - "str25", - "str26" - ], - "ByteSlice": "Gxwd", - "Small": { - "Tag": "tag30" - }, - "PSmall": { - "Tag": "tag31" - }, - "PPSmall": null, - "Interface": 5.2, - "PInterface": null -}` - -var allValueCompact = stripWhitespace(allValueIndent) - -var pallValueIndent = `{ - "Bool": false, - "Int": 0, - "Int8": 0, - "Int16": 0, - "Int32": 0, - "Int64": 0, - "Uint": 0, - "Uint8": 0, - "Uint16": 0, - "Uint32": 0, - "Uint64": 0, - "Uintptr": 0, - "Float32": 0, - "Float64": 0, - "bar": "", - "bar2": "", - "IntStr": "0", - "UintptrStr": "0", - "PBool": true, - "PInt": 2, - "PInt8": 3, - "PInt16": 4, - "PInt32": 5, - "PInt64": 6, - "PUint": 7, - "PUint8": 8, - "PUint16": 9, - "PUint32": 10, - "PUint64": 11, - "PUintptr": 12, - "PFloat32": 14.1, - "PFloat64": 15.1, - "String": "", - "PString": "16", - "Map": null, - "MapP": null, - "PMap": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "PMapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "EmptyMap": null, - "NilMap": null, - "Slice": null, - "SliceP": null, - "PSlice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "PSliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "EmptySlice": null, - "NilSlice": null, - "StringSlice": null, - "ByteSlice": null, - "Small": { - "Tag": "" - }, - "PSmall": null, - "PPSmall": { - "Tag": "tag31" - }, - "Interface": null, - "PInterface": 5.2 -}` - -var pallValueCompact = stripWhitespace(pallValueIndent) - -func TestRefUnmarshal(t *testing.T) { - type S struct { - // Ref is defined in encode_test.go. - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - } - want := S{ - R0: 12, - R1: new(Ref), - R2: 13, - R3: new(RefText), - } - *want.R1 = 12 - *want.R3 = 13 - - var got S - if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("Unmarsha:\n\tgot: %+v\n\twant: %+v", got, want) - } -} - -// Test that the empty string doesn't panic decoding when ,string is specified -// Issue 3450 -func TestEmptyString(t *testing.T) { - type T2 struct { - Number1 int `json:",string"` - Number2 int `json:",string"` - } - data := `{"Number1":"1", "Number2":""}` - dec := NewDecoder(strings.NewReader(data)) - var got T2 - switch err := dec.Decode(&got); { - case err == nil: - t.Fatalf("Decode error: got nil, want non-nil") - case got.Number1 != 1: - t.Fatalf("Decode: got.Number1 = %d, want 1", got.Number1) - } -} - -// Test that a null for ,string is not replaced with the previous quoted string (issue 7046). -// It should also not be an error (issue 2540, issue 8587). -func TestNullString(t *testing.T) { - type T struct { - A int `json:",string"` - B int `json:",string"` - C *int `json:",string"` - } - data := []byte(`{"A": "1", "B": null, "C": null}`) - var s T - s.B = 1 - s.C = new(int) - *s.C = 2 - switch err := Unmarshal(data, &s); { - case err != nil: - t.Fatalf("Unmarshal error: %v", err) - case s.B != 1: - t.Fatalf("Unmarshal: s.B = %d, want 1", s.B) - case s.C != nil: - t.Fatalf("Unmarshal: s.C = %d, want non-nil", s.C) - } -} - -func addr[T any](v T) *T { - return &v -} - -func TestInterfaceSet(t *testing.T) { - errUnmarshal := &UnmarshalTypeError{Value: "object", Offset: 6, Type: reflect.TypeFor[int](), Field: "X"} - tests := []struct { - CaseName - pre any - json string - post any - }{ - {Name(""), "foo", `"bar"`, "bar"}, - {Name(""), "foo", `2`, 2.0}, - {Name(""), "foo", `true`, true}, - {Name(""), "foo", `null`, nil}, - {Name(""), map[string]any{}, `true`, true}, - {Name(""), []string{}, `true`, true}, - - {Name(""), any(nil), `null`, any(nil)}, - {Name(""), (*int)(nil), `null`, any(nil)}, - {Name(""), (*int)(addr(0)), `null`, any(nil)}, - {Name(""), (*int)(addr(1)), `null`, any(nil)}, - {Name(""), (**int)(nil), `null`, any(nil)}, - {Name(""), (**int)(addr[*int](nil)), `null`, (**int)(addr[*int](nil))}, - {Name(""), (**int)(addr(addr(1))), `null`, (**int)(addr[*int](nil))}, - {Name(""), (***int)(nil), `null`, any(nil)}, - {Name(""), (***int)(addr[**int](nil)), `null`, (***int)(addr[**int](nil))}, - {Name(""), (***int)(addr(addr[*int](nil))), `null`, (***int)(addr[**int](nil))}, - {Name(""), (***int)(addr(addr(addr(1)))), `null`, (***int)(addr[**int](nil))}, - - {Name(""), any(nil), `2`, float64(2)}, - {Name(""), (int)(1), `2`, float64(2)}, - {Name(""), (*int)(nil), `2`, float64(2)}, - {Name(""), (*int)(addr(0)), `2`, (*int)(addr(2))}, - {Name(""), (*int)(addr(1)), `2`, (*int)(addr(2))}, - {Name(""), (**int)(nil), `2`, float64(2)}, - {Name(""), (**int)(addr[*int](nil)), `2`, (**int)(addr(addr(2)))}, - {Name(""), (**int)(addr(addr(1))), `2`, (**int)(addr(addr(2)))}, - {Name(""), (***int)(nil), `2`, float64(2)}, - {Name(""), (***int)(addr[**int](nil)), `2`, (***int)(addr(addr(addr(2))))}, - {Name(""), (***int)(addr(addr[*int](nil))), `2`, (***int)(addr(addr(addr(2))))}, - {Name(""), (***int)(addr(addr(addr(1)))), `2`, (***int)(addr(addr(addr(2))))}, - - {Name(""), any(nil), `{}`, map[string]any{}}, - {Name(""), (int)(1), `{}`, map[string]any{}}, - {Name(""), (*int)(nil), `{}`, map[string]any{}}, - {Name(""), (*int)(addr(0)), `{}`, errUnmarshal}, - {Name(""), (*int)(addr(1)), `{}`, errUnmarshal}, - {Name(""), (**int)(nil), `{}`, map[string]any{}}, - {Name(""), (**int)(addr[*int](nil)), `{}`, errUnmarshal}, - {Name(""), (**int)(addr(addr(1))), `{}`, errUnmarshal}, - {Name(""), (***int)(nil), `{}`, map[string]any{}}, - {Name(""), (***int)(addr[**int](nil)), `{}`, errUnmarshal}, - {Name(""), (***int)(addr(addr[*int](nil))), `{}`, errUnmarshal}, - {Name(""), (***int)(addr(addr(addr(1)))), `{}`, errUnmarshal}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b := struct{ X any }{tt.pre} - blob := `{"X":` + tt.json + `}` - if err := Unmarshal([]byte(blob), &b); err != nil { - if wantErr, _ := tt.post.(error); equalError(err, wantErr) { - return - } - t.Fatalf("%s: Unmarshal(%#q) error: %v", tt.Where, blob, err) - } - if !reflect.DeepEqual(b.X, tt.post) { - t.Errorf("%s: Unmarshal(%#q):\n\tpre.X: %#v\n\tgot.X: %#v\n\twant.X: %#v", tt.Where, blob, tt.pre, b.X, tt.post) - } - }) - } -} - -type NullTest struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Float32 float32 - Float64 float64 - String string - PBool *bool - Map map[string]string - Slice []string - Interface any - - PRaw *RawMessage - PTime *time.Time - PBigInt *big.Int - PText *MustNotUnmarshalText - PBuffer *bytes.Buffer // has methods, just not relevant ones - PStruct *struct{} - - Raw RawMessage - Time time.Time - BigInt big.Int - Text MustNotUnmarshalText - Buffer bytes.Buffer - Struct struct{} -} - -// JSON null values should be ignored for primitives and string values instead of resulting in an error. -// Issue 2540 -func TestUnmarshalNulls(t *testing.T) { - // Unmarshal docs: - // 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 - // on the value and produces no error. - - jsonData := []byte(`{ - "Bool" : null, - "Int" : null, - "Int8" : null, - "Int16" : null, - "Int32" : null, - "Int64" : null, - "Uint" : null, - "Uint8" : null, - "Uint16" : null, - "Uint32" : null, - "Uint64" : null, - "Float32" : null, - "Float64" : null, - "String" : null, - "PBool": null, - "Map": null, - "Slice": null, - "Interface": null, - "PRaw": null, - "PTime": null, - "PBigInt": null, - "PText": null, - "PBuffer": null, - "PStruct": null, - "Raw": null, - "Time": null, - "BigInt": null, - "Text": null, - "Buffer": null, - "Struct": null - }`) - nulls := NullTest{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Float32: 12.1, - Float64: 13.1, - String: "14", - PBool: new(bool), - Map: map[string]string{}, - Slice: []string{}, - Interface: new(MustNotUnmarshalJSON), - PRaw: new(RawMessage), - PTime: new(time.Time), - PBigInt: new(big.Int), - PText: new(MustNotUnmarshalText), - PStruct: new(struct{}), - PBuffer: new(bytes.Buffer), - Raw: RawMessage("123"), - Time: time.Unix(123456789, 0), - BigInt: *big.NewInt(123), - } - - before := nulls.Time.String() - - err := Unmarshal(jsonData, &nulls) - if err != nil { - t.Errorf("Unmarshal of null values failed: %v", err) - } - if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || - nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || - nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { - t.Errorf("Unmarshal of null values affected primitives") - } - - if nulls.PBool != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBool") - } - if nulls.Map != nil { - t.Errorf("Unmarshal of null did not clear nulls.Map") - } - if nulls.Slice != nil { - t.Errorf("Unmarshal of null did not clear nulls.Slice") - } - if nulls.Interface != nil { - t.Errorf("Unmarshal of null did not clear nulls.Interface") - } - if nulls.PRaw != nil { - t.Errorf("Unmarshal of null did not clear nulls.PRaw") - } - if nulls.PTime != nil { - t.Errorf("Unmarshal of null did not clear nulls.PTime") - } - if nulls.PBigInt != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBigInt") - } - if nulls.PText != nil { - t.Errorf("Unmarshal of null did not clear nulls.PText") - } - if nulls.PBuffer != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBuffer") - } - if nulls.PStruct != nil { - t.Errorf("Unmarshal of null did not clear nulls.PStruct") - } - - if string(nulls.Raw) != "null" { - t.Errorf("Unmarshal of RawMessage null did not record null: %v", string(nulls.Raw)) - } - if nulls.Time.String() != before { - t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) - } - if nulls.BigInt.String() != "123" { - t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) - } -} - -type MustNotUnmarshalJSON struct{} - -func (x MustNotUnmarshalJSON) UnmarshalJSON(data []byte) error { - return errors.New("MustNotUnmarshalJSON was used") -} - -type MustNotUnmarshalText struct{} - -func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { - return errors.New("MustNotUnmarshalText was used") -} - -func TestStringKind(t *testing.T) { - type stringKind string - want := map[stringKind]int{"foo": 42} - data, err := Marshal(want) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got map[stringKind]int - err = Unmarshal(data, &got) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !maps.Equal(got, want) { - t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) - } -} - -// Custom types with []byte as underlying type could not be marshaled -// and then unmarshaled. -// Issue 8962. -func TestByteKind(t *testing.T) { - type byteKind []byte - want := byteKind("hello") - data, err := Marshal(want) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got byteKind - err = Unmarshal(data, &got) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !slices.Equal(got, want) { - t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) - } -} - -// The fix for issue 8962 introduced a regression. -// Issue 12921. -func TestSliceOfCustomByte(t *testing.T) { - type Uint8 uint8 - want := []Uint8("hello") - data, err := Marshal(want) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got []Uint8 - err = Unmarshal(data, &got) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !slices.Equal(got, want) { - t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) - } -} - -func TestUnmarshalTypeError(t *testing.T) { - tests := []struct { - CaseName - dest any - in string - }{ - {Name(""), new(string), `{"user": "name"}`}, // issue 4628. - {Name(""), new(error), `{}`}, // issue 4222 - {Name(""), new(error), `[]`}, - {Name(""), new(error), `""`}, - {Name(""), new(error), `123`}, - {Name(""), new(error), `true`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.in), tt.dest) - if _, ok := err.(*UnmarshalTypeError); !ok { - t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %T\n\twant: %T", - tt.Where, tt.in, tt.dest, err, new(UnmarshalTypeError)) - } - }) - } -} - -func TestUnmarshalSyntax(t *testing.T) { - var x any - tests := []struct { - CaseName - in string - }{ - {Name(""), "tru"}, - {Name(""), "fals"}, - {Name(""), "nul"}, - {Name(""), "123e"}, - {Name(""), `"hello`}, - {Name(""), `[1,2,3`}, - {Name(""), `{"key":1`}, - {Name(""), `{"key":1,`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.in), &x) - if _, ok := err.(*SyntaxError); !ok { - t.Errorf("%s: Unmarshal(%#q, any):\n\tgot: %T\n\twant: %T", - tt.Where, tt.in, err, new(SyntaxError)) - } - }) - } -} - -// Test handling of unexported fields that should be ignored. -// Issue 4660 -type unexportedFields struct { - Name string - m map[string]any `json:"-"` - m2 map[string]any `json:"abcd"` - - s []int `json:"-"` -} - -func TestUnmarshalUnexported(t *testing.T) { - input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` - want := &unexportedFields{Name: "Bob"} - - out := &unexportedFields{} - err := Unmarshal([]byte(input), out) - if err != nil { - t.Errorf("Unmarshal error: %v", err) - } - if !reflect.DeepEqual(out, want) { - t.Errorf("Unmarshal:\n\tgot: %+v\n\twant: %+v", out, want) - } -} - -// Time3339 is a time.Time which encodes to and from JSON -// as an RFC 3339 time in UTC. -type Time3339 time.Time - -func (t *Time3339) UnmarshalJSON(b []byte) error { - if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { - return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) - } - tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) - if err != nil { - return err - } - *t = Time3339(tm) - return nil -} - -func TestUnmarshalJSONLiteralError(t *testing.T) { - var t3 Time3339 - switch err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3); { - case err == nil: - t.Fatalf("Unmarshal error: got nil, want non-nil") - case !strings.Contains(err.Error(), "range"): - t.Errorf("Unmarshal error:\n\tgot: %v\n\twant: out of range", err) - } -} - -// Test that extra object elements in an array do not result in a -// "data changing underfoot" error. -// Issue 3717 -func TestSkipArrayObjects(t *testing.T) { - json := `[{}]` - var dest [0]any - - err := Unmarshal([]byte(json), &dest) - if err != nil { - t.Errorf("Unmarshal error: %v", err) - } -} - -// Test semantics of pre-filled data, such as struct fields, map elements, -// slices, and arrays. -// Issues 4900 and 8837, among others. -func TestPrefilled(t *testing.T) { - // Values here change, cannot reuse table across runs. - tests := []struct { - CaseName - in string - ptr any - out any - }{{ - CaseName: Name(""), - in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, - }, { - CaseName: Name(""), - in: `{"X": 1, "Y": 2}`, - 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}, - }, { - CaseName: Name(""), - in: `[2]`, - ptr: &[]int{1}, - out: &[]int{2}, - }, { - CaseName: Name(""), - in: `[2, 3]`, - ptr: &[]int{1}, - out: &[]int{2, 3}, - }, { - CaseName: Name(""), - in: `[2, 3]`, - ptr: &[...]int{1}, - out: &[...]int{2}, - }, { - CaseName: Name(""), - in: `[3]`, - ptr: &[...]int{1, 2}, - out: &[...]int{3, 0}, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - ptrstr := fmt.Sprintf("%v", tt.ptr) - err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here - if err != nil { - t.Errorf("%s: Unmarshal error: %v", tt.Where, err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %v\n\twant: %v", tt.Where, tt.in, ptrstr, tt.ptr, tt.out) - } - }) - } -} - -func TestInvalidUnmarshal(t *testing.T) { - tests := []struct { - CaseName - in string - v any - wantErr error - }{ - {Name(""), `{"a":"1"}`, nil, &InvalidUnmarshalError{}}, - {Name(""), `{"a":"1"}`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, - {Name(""), `{"a":"1"}`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, - {Name(""), `123`, nil, &InvalidUnmarshalError{}}, - {Name(""), `123`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, - {Name(""), `123`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, - {Name(""), `123`, new(net.IP), &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[*net.IP](), Offset: 3}}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - switch gotErr := Unmarshal([]byte(tt.in), tt.v); { - case gotErr == nil: - t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) - case !reflect.DeepEqual(gotErr, tt.wantErr): - t.Errorf("%s: Unmarshal error:\n\tgot: %#v\n\twant: %#v", tt.Where, gotErr, tt.wantErr) - } - }) - } -} - -// Test that string option is ignored for invalid types. -// Issue 9812. -func TestInvalidStringOption(t *testing.T) { - num := 0 - item := struct { - T time.Time `json:",string"` - M map[string]string `json:",string"` - S []string `json:",string"` - A [1]string `json:",string"` - I any `json:",string"` - P *int `json:",string"` - }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} - - data, err := Marshal(item) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - - err = Unmarshal(data, &item) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } -} - -// Test unmarshal behavior with regards to embedded unexported structs. -// -// (Issue 21357) If the embedded struct is a pointer and is unallocated, -// this returns an error because unmarshal cannot set the field. -// -// (Issue 24152) If the embedded struct is given an explicit name, -// ensure that the normal unmarshal logic does not panic in reflect. -// -// (Issue 28145) If the embedded struct is given an explicit name and has -// exported methods, don't cause a panic trying to get its value. -func TestUnmarshalEmbeddedUnexported(t *testing.T) { - type ( - embed1 struct{ Q int } - embed2 struct{ Q int } - embed3 struct { - Q int64 `json:",string"` - } - S1 struct { - *embed1 - R int - } - S2 struct { - *embed1 - Q int - } - S3 struct { - embed1 - R int - } - S4 struct { - *embed1 - embed2 - } - S5 struct { - *embed3 - R int - } - S6 struct { - embed1 `json:"embed1"` - } - S7 struct { - embed1 `json:"embed1"` - embed2 - } - S8 struct { - embed1 `json:"embed1"` - embed2 `json:"embed2"` - Q int - } - S9 struct { - unexportedWithMethods `json:"embed"` - } - ) - - tests := []struct { - CaseName - in string - ptr any - out any - err error - }{{ - // Error since we cannot set S1.embed1, but still able to set S1.R. - CaseName: Name(""), - in: `{"R":2,"Q":1}`, - ptr: new(S1), - out: &S1{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), - }, { - // The top level Q field takes precedence. - CaseName: Name(""), - in: `{"Q":1}`, - ptr: new(S2), - out: &S2{Q: 1}, - }, { - // No issue with non-pointer variant. - CaseName: Name(""), - in: `{"R":2,"Q":1}`, - ptr: new(S3), - out: &S3{embed1: embed1{Q: 1}, R: 2}, - }, { - // No error since both embedded structs have field R, which annihilate each other. - // Thus, no attempt is made at setting S4.embed1. - CaseName: Name(""), - in: `{"R":2}`, - ptr: new(S4), - out: new(S4), - }, { - // Error since we cannot set S5.embed1, but still able to set S5.R. - CaseName: Name(""), - in: `{"R":2,"Q":1}`, - ptr: new(S5), - out: &S5{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), - }, { - // Issue 24152, ensure decodeState.indirect does not panic. - CaseName: Name(""), - in: `{"embed1": {"Q": 1}}`, - ptr: new(S6), - out: &S6{embed1{1}}, - }, { - // Issue 24153, check that we can still set forwarded fields even in - // the presence of a name conflict. - // - // This relies on obscure behavior of reflect where it is possible - // to set a forwarded exported field on an unexported embedded struct - // even though there is a name conflict, even when it would have been - // impossible to do so according to Go visibility rules. - // Go forbids this because it is ambiguous whether S7.Q refers to - // S7.embed1.Q or S7.embed2.Q. Since embed1 and embed2 are unexported, - // it should be impossible for an external package to set either Q. - // - // It is probably okay for a future reflect change to break this. - CaseName: Name(""), - in: `{"embed1": {"Q": 1}, "Q": 2}`, - ptr: new(S7), - out: &S7{embed1{1}, embed2{2}}, - }, { - // Issue 24153, similar to the S7 case. - CaseName: Name(""), - in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, - ptr: new(S8), - out: &S8{embed1{1}, embed2{2}, 3}, - }, { - // Issue 228145, similar to the cases above. - CaseName: Name(""), - in: `{"embed": {}}`, - ptr: new(S9), - out: &S9{}, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.in), tt.ptr) - if !equalError(err, tt.err) { - t.Errorf("%s: Unmarshal error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("%s: Unmarshal:\n\tgot: %#+v\n\twant: %#+v", tt.Where, tt.ptr, tt.out) - } - }) - } -} - -func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { - tests := []struct { - CaseName - in string - err error - }{{ - CaseName: Name(""), - in: `1 false null :`, - err: &SyntaxError{"invalid character ':' looking for beginning of value", 14}, - }, { - CaseName: Name(""), - in: `1 [] [,]`, - err: &SyntaxError{"invalid character ',' looking for beginning of value", 7}, - }, { - CaseName: Name(""), - in: `1 [] [true:]`, - err: &SyntaxError{"invalid character ':' after array element", 11}, - }, { - CaseName: Name(""), - in: `1 {} {"x"=}`, - err: &SyntaxError{"invalid character '=' after object key", 14}, - }, { - CaseName: Name(""), - in: `falsetruenul#`, - err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", 13}, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - dec := NewDecoder(strings.NewReader(tt.in)) - var err error - for err == nil { - var v any - err = dec.Decode(&v) - } - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("%s: Decode error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) - } - }) - } -} - -type unmarshalPanic struct{} - -func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) } - -func TestUnmarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - Unmarshal([]byte("{}"), &unmarshalPanic{}) - t.Fatalf("Unmarshal should have panicked") -} - -// 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 any - v = &v - data := []byte(`{"a": "b"}`) - - if err := Unmarshal(data, v); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } -} - -type textUnmarshalerString string - -func (m *textUnmarshalerString) UnmarshalText(text []byte) error { - *m = textUnmarshalerString(strings.ToLower(string(text))) - return nil -} - -// Test unmarshal to a map, where the map key is a user defined type. -// See golang.org/issues/34437. -func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - - if _, ok := p["foo"]; !ok { - t.Errorf(`key "foo" missing in map: %v`, p) - } -} - -func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { - // See golang.org/issues/38105. - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if _, ok := p["开源"]; !ok { - t.Errorf(`key "开源" missing in map: %v`, p) - } - - // See golang.org/issues/38126. - type T struct { - F1 string `json:"F1,string"` - } - wantT := T{"aaa\tbbb"} - - b, err := Marshal(wantT) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var gotT T - if err := Unmarshal(b, &gotT); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if gotT != wantT { - t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) - } - - // See golang.org/issues/39555. - input := map[textUnmarshalerString]string{"FOO": "", `"`: ""} - - encoded, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got map[textUnmarshalerString]string - if err := Unmarshal(encoded, &got); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - want := map[textUnmarshalerString]string{"foo": "", `"`: ""} - if !maps.Equal(got, want) { - t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) - } -} - -func TestUnmarshalMaxDepth(t *testing.T) { - tests := []struct { - CaseName - data string - errMaxDepth bool - }{{ - CaseName: Name("ArrayUnderMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, - errMaxDepth: false, - }, { - CaseName: Name("ArrayOverMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, - errMaxDepth: true, - }, { - CaseName: Name("ArrayOverStackDepth"), - data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, - errMaxDepth: true, - }, { - CaseName: Name("ObjectUnderMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, - errMaxDepth: false, - }, { - CaseName: Name("ObjectOverMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, - errMaxDepth: true, - }, { - CaseName: Name("ObjectOverStackDepth"), - data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, - errMaxDepth: true, - }} - - targets := []struct { - CaseName - newValue func() any - }{{ - CaseName: Name("unstructured"), - newValue: func() any { - var v any - return &v - }, - }, { - CaseName: Name("typed named field"), - newValue: func() any { - v := struct { - A any `json:"a"` - }{} - return &v - }, - }, { - CaseName: Name("typed missing field"), - newValue: func() any { - v := struct { - B any `json:"b"` - }{} - return &v - }, - }, { - CaseName: Name("custom unmarshaler"), - newValue: func() any { - v := unmarshaler{} - return &v - }, - }} - - for _, tt := range tests { - for _, target := range targets { - t.Run(target.Name+"-"+tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.data), target.newValue()) - if !tt.errMaxDepth { - if err != nil { - t.Errorf("%s: %s: Unmarshal error: %v", tt.Where, target.Where, err) - } - } else { - if err == nil || !strings.Contains(err.Error(), "exceeded max depth") { - t.Errorf("%s: %s: Unmarshal error:\n\tgot: %v\n\twant: exceeded max depth", tt.Where, target.Where, err) - } - } - }) - } - } -} diff --git a/pkg/encoders/json/encode_test.go b/pkg/encoders/json/encode_test.go deleted file mode 100644 index 87074ea..0000000 --- a/pkg/encoders/json/encode_test.go +++ /dev/null @@ -1,1425 +0,0 @@ -// Copyright 2011 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. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "bytes" - "encoding" - "fmt" - "log" - "math" - "reflect" - "regexp" - "runtime/debug" - "strconv" - "sync" - "testing" - "testing/synctest" - "time" -) - -type OptionalsEmpty struct { - 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"` - - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitempty"` - - Mr map[string]any `json:"mr"` - Mo map[string]any `json:",omitempty"` - - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitempty"` - - Br bool `json:"br"` - Bo bool `json:"bo,omitempty"` - - Ur uint `json:"ur"` - Uo uint `json:"uo,omitempty"` - - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty"` -} - -func TestOmitEmpty(t *testing.T) { - const want = `{ - "sr": "", - "omitempty": 0, - "slr": null, - "mr": {}, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "sto": {} -}` - var o OptionalsEmpty - o.Sw = "something" - o.Mr = map[string]any{} - o.Mo = map[string]any{} - - got, err := MarshalIndent(&o, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -type NonZeroStruct struct{} - -func (nzs NonZeroStruct) IsZero() bool { - return false -} - -type NoPanicStruct struct { - Int int `json:"int,omitzero"` -} - -func (nps *NoPanicStruct) IsZero() bool { - return nps.Int != 0 -} - -type OptionalsZero struct { - Sr string `json:"sr"` - So string `json:"so,omitzero"` - Sw string `json:"-"` - - Ir int `json:"omitzero"` // actually named omitzero, not an option - Io int `json:"io,omitzero"` - - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitzero"` - SloNonNil []string `json:"slononnil,omitzero"` - - Mr map[string]any `json:"mr"` - Mo map[string]any `json:",omitzero"` - Moo map[string]any `json:"moo,omitzero"` - - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitzero"` - Foo float64 `json:"foo,omitzero"` - Foo2 [2]float64 `json:"foo2,omitzero"` - - Br bool `json:"br"` - Bo bool `json:"bo,omitzero"` - - Ur uint `json:"ur"` - Uo uint `json:"uo,omitzero"` - - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitzero"` - - Time time.Time `json:"time,omitzero"` - TimeLocal time.Time `json:"timelocal,omitzero"` - Nzs NonZeroStruct `json:"nzs,omitzero"` - - NilIsZeroer isZeroer `json:"niliszeroer,omitzero"` // nil interface - NonNilIsZeroer isZeroer `json:"nonniliszeroer,omitzero"` // non-nil interface - NoPanicStruct0 isZeroer `json:"nps0,omitzero"` // non-nil interface with nil pointer - NoPanicStruct1 isZeroer `json:"nps1,omitzero"` // non-nil interface with non-nil pointer - NoPanicStruct2 *NoPanicStruct `json:"nps2,omitzero"` // nil pointer - NoPanicStruct3 *NoPanicStruct `json:"nps3,omitzero"` // non-nil pointer - NoPanicStruct4 NoPanicStruct `json:"nps4,omitzero"` // concrete type -} - -func TestOmitZero(t *testing.T) { - const want = `{ - "sr": "", - "omitzero": 0, - "slr": null, - "slononnil": [], - "mr": {}, - "Mo": {}, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "nzs": {}, - "nps1": {}, - "nps3": {}, - "nps4": {} -}` - var o OptionalsZero - o.Sw = "something" - o.SloNonNil = make([]string, 0) - o.Mr = map[string]any{} - o.Mo = map[string]any{} - - o.Foo = -0 - o.Foo2 = [2]float64{+0, -0} - - o.TimeLocal = time.Time{}.Local() - - o.NonNilIsZeroer = time.Time{} - o.NoPanicStruct0 = (*NoPanicStruct)(nil) - o.NoPanicStruct1 = &NoPanicStruct{} - o.NoPanicStruct3 = &NoPanicStruct{} - - got, err := MarshalIndent(&o, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -func TestOmitZeroMap(t *testing.T) { - const want = `{ - "foo": { - "sr": "", - "omitzero": 0, - "slr": null, - "mr": null, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "nzs": {}, - "nps4": {} - } -}` - m := map[string]OptionalsZero{"foo": {}} - got, err := MarshalIndent(m, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - fmt.Println(got) - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -type OptionalsEmptyZero struct { - Sr string `json:"sr"` - So string `json:"so,omitempty,omitzero"` - Sw string `json:"-"` - - Io int `json:"io,omitempty,omitzero"` - - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitempty,omitzero"` - SloNonNil []string `json:"slononnil,omitempty,omitzero"` - - Mr map[string]any `json:"mr"` - Mo map[string]any `json:",omitempty,omitzero"` - - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitempty,omitzero"` - - Br bool `json:"br"` - Bo bool `json:"bo,omitempty,omitzero"` - - Ur uint `json:"ur"` - Uo uint `json:"uo,omitempty,omitzero"` - - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty,omitzero"` - - Time time.Time `json:"time,omitempty,omitzero"` - Nzs NonZeroStruct `json:"nzs,omitempty,omitzero"` -} - -func TestOmitEmptyZero(t *testing.T) { - const want = `{ - "sr": "", - "slr": null, - "mr": {}, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "nzs": {} -}` - var o OptionalsEmptyZero - o.Sw = "something" - o.SloNonNil = make([]string, 0) - o.Mr = map[string]any{} - o.Mo = map[string]any{} - - got, err := MarshalIndent(&o, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -type StringTag struct { - BoolStr bool `json:",string"` - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - StrStr string `json:",string"` - NumberStr Number `json:",string"` -} - -func TestRoundtripStringTag(t *testing.T) { - tests := []struct { - CaseName - in StringTag - want string // empty to just test that we roundtrip - }{{ - CaseName: 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. - CaseName: 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": "\"\\b\\f\\n\\r\\t\\\"\\\\\"", - "NumberStr": "0" -}`, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - got, err := MarshalIndent(&tt.in, "", "\t") - if err != nil { - t.Fatalf("%s: MarshalIndent error: %v", tt.Where, err) - } - if got := string(got); got != tt.want { - t.Fatalf("%s: MarshalIndent:\n\tgot: %s\n\twant: %s", tt.Where, stripWhitespace(got), stripWhitespace(tt.want)) - } - - // Verify that it round-trips. - var s2 StringTag - if err := Unmarshal(got, &s2); err != nil { - t.Fatalf("%s: Decode error: %v", tt.Where, err) - } - if !reflect.DeepEqual(s2, tt.in) { - t.Fatalf("%s: Decode:\n\tinput: %s\n\tgot: %#v\n\twant: %#v", tt.Where, indentNewlines(string(got)), s2, tt.in) - } - }) - } -} - -// byte slices are special even if they're renamed types. -type renamedByte byte -type renamedByteSlice []byte -type renamedRenamedByteSlice []renamedByte - -func TestEncodeRenamedByteSlice(t *testing.T) { - s := renamedByteSlice("abc") - got, err := Marshal(s) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - want := `"YWJj"` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } - r := renamedRenamedByteSlice("abc") - got, err = Marshal(r) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -type SamePointerNoCycle struct { - Ptr1, Ptr2 *SamePointerNoCycle -} - -var samePointerNoCycle = &SamePointerNoCycle{} - -type PointerCycle struct { - Ptr *PointerCycle -} - -var pointerCycle = &PointerCycle{} - -type PointerCycleIndirect struct { - Ptrs []any -} - -type RecursiveSlice []RecursiveSlice - -var ( - pointerCycleIndirect = &PointerCycleIndirect{} - mapCycle = make(map[string]any) - sliceCycle = []any{nil} - sliceNoCycle = []any{nil, nil} - recursiveSliceCycle = []RecursiveSlice{nil} -) - -func init() { - ptr := &SamePointerNoCycle{} - samePointerNoCycle.Ptr1 = ptr - samePointerNoCycle.Ptr2 = ptr - - pointerCycle.Ptr = pointerCycle - pointerCycleIndirect.Ptrs = []any{pointerCycleIndirect} - - mapCycle["x"] = mapCycle - sliceCycle[0] = sliceCycle - sliceNoCycle[1] = sliceNoCycle[:1] - for i := startDetectingCyclesAfter; i > 0; i-- { - sliceNoCycle = []any{sliceNoCycle} - } - recursiveSliceCycle[0] = recursiveSliceCycle -} - -func TestSamePointerNoCycle(t *testing.T) { - if _, err := Marshal(samePointerNoCycle); err != nil { - t.Fatalf("Marshal error: %v", err) - } -} - -func TestSliceNoCycle(t *testing.T) { - if _, err := Marshal(sliceNoCycle); err != nil { - t.Fatalf("Marshal error: %v", err) - } -} - -func TestUnsupportedValues(t *testing.T) { - tests := []struct { - CaseName - in any - }{ - {Name(""), math.NaN()}, - {Name(""), math.Inf(-1)}, - {Name(""), math.Inf(1)}, - {Name(""), pointerCycle}, - {Name(""), pointerCycleIndirect}, - {Name(""), mapCycle}, - {Name(""), sliceCycle}, - {Name(""), recursiveSliceCycle}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - if _, err := Marshal(tt.in); err != nil { - if _, ok := err.(*UnsupportedValueError); !ok { - t.Errorf("%s: Marshal error:\n\tgot: %T\n\twant: %T", tt.Where, err, new(UnsupportedValueError)) - } - } else { - t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where) - } - }) - } -} - -// Issue 43207 -func TestMarshalTextFloatMap(t *testing.T) { - m := map[textfloat]string{ - textfloat(math.NaN()): "1", - textfloat(math.NaN()): "1", - } - got, err := Marshal(m) - if err != nil { - t.Errorf("Marshal error: %v", err) - } - want := `{"TF:NaN":"1","TF:NaN":"1"}` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -// Ref has Marshaler and Unmarshaler methods with pointer receiver. -type Ref int - -func (*Ref) MarshalJSON() ([]byte, error) { - return []byte(`"ref"`), nil -} - -func (r *Ref) UnmarshalJSON([]byte) error { - *r = 12 - return nil -} - -// Val has Marshaler methods with value receiver. -type Val int - -func (Val) MarshalJSON() ([]byte, error) { - 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 -} - -func (r *RefText) UnmarshalText([]byte) error { - *r = 13 - return nil -} - -// ValText has Marshaler methods with value receiver. -type ValText int - -func (ValText) MarshalText() ([]byte, error) { - 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 error: %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -// C implements Marshaler and returns unescaped JSON. -type C int - -func (C) MarshalJSON() ([]byte, error) { - return []byte(`"<&>"`), nil -} - -// CText implements Marshaler and returns unescaped text. -type CText int - -func (CText) MarshalText() ([]byte, error) { - return []byte(`"<&>"`), nil -} - -func TestMarshalerEscaping(t *testing.T) { - var c C - want := `"\u003c\u0026\u003e"` - b, err := Marshal(c) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } - - var ct CText - want = `"\"\u003c\u0026\u003e\""` - b, err = Marshal(ct) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestAnonymousFields(t *testing.T) { - tests := []struct { - CaseName - 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. - CaseName: Name("AmbiguousField"), - makeInput: func() any { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - } - ) - return S{S1{1, 2}, S2{3, 4}} - }, - want: `{}`, - }, { - CaseName: Name("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() any { - 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. - CaseName: Name("UnexportedEmbeddedInt"), - makeInput: func() any { - type ( - myInt int - S struct{ myInt } - ) - return S{5} - }, - want: `{}`, - }, { - // Exported embedded field of non-struct type should be serialized. - CaseName: Name("ExportedEmbeddedInt"), - makeInput: func() any { - 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. - CaseName: Name("UnexportedEmbeddedIntPointer"), - makeInput: func() any { - 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. - CaseName: Name("ExportedEmbeddedIntPointer"), - makeInput: func() any { - 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. - CaseName: Name("EmbeddedStruct"), - makeInput: func() any { - 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. - CaseName: Name("EmbeddedStructPointer"), - makeInput: func() any { - 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. - CaseName: Name("NestedStructAndInts"), - makeInput: func() any { - 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. - CaseName: Name("EmbeddedFieldBehindNilPointer"), - makeInput: func() any { - type ( - S2 struct{ Field string } - S struct{ *S2 } - ) - return S{} - }, - want: `{}`, - }} - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.makeInput()) - if err != nil { - t.Fatalf("%s: Marshal error: %v", tt.Where, err) - } - if string(b) != tt.want { - t.Fatalf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, b, tt.want) - } - }) - } -} - -type BugA struct { - S string -} - -type BugB struct { - BugA - S string -} - -type BugC struct { - S string -} - -// Legal Go: We never use the repeated embedded field (S). -type BugX struct { - A int - BugA - BugB -} - -// golang.org/issue/16042. -// Even if a nil interface value is passed in, as long as -// it implements Marshaler, it should be marshaled. -type nilJSONMarshaler string - -func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { - if nm == nil { - return Marshal("0zenil0") - } - return Marshal("zenil:" + string(*nm)) -} - -// golang.org/issue/34235. -// Even if a nil interface value is passed in, as long as -// it implements encoding.TextMarshaler, it should be marshaled. -type nilTextMarshaler string - -func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { - 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) { - tests := []struct { - CaseName - in any - want string - }{ - {Name(""), nil, `null`}, - {Name(""), new(float64), `0`}, - {Name(""), []any(nil), `null`}, - {Name(""), []string(nil), `null`}, - {Name(""), map[string]string(nil), `null`}, - {Name(""), []byte(nil), `null`}, - {Name(""), struct{ M string }{"gopher"}, `{"M":"gopher"}`}, - {Name(""), struct{ M Marshaler }{}, `{"M":null}`}, - {Name(""), struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, `{"M":"0zenil0"}`}, - {Name(""), struct{ M any }{(*nilJSONMarshaler)(nil)}, `{"M":null}`}, - {Name(""), struct{ M encoding.TextMarshaler }{}, `{"M":null}`}, - {Name(""), struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, `{"M":"0zenil0"}`}, - {Name(""), struct{ M any }{(*nilTextMarshaler)(nil)}, `{"M":null}`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - switch got, err := Marshal(tt.in); { - case err != nil: - t.Fatalf("%s: Marshal error: %v", tt.Where, err) - case string(got) != tt.want: - t.Fatalf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) - } - }) - } -} - -// Issue 5245. -func TestEmbeddedBug(t *testing.T) { - v := BugB{ - BugA{"A"}, - "B", - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal error:", err) - } - want := `{"S":"B"}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %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 error:", err) - } - want = `{"A":23}` - got = string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -type BugD struct { // Same as BugA after tagging. - XXX string `json:"S"` -} - -// BugD's tagged S field should dominate BugA's. -type BugY struct { - 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 error:", err) - } - want := `{"S":"BugD"}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %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. -} - -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 error:", err) - } - want := `{}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestIssue10281(t *testing.T) { - type Foo struct { - N Number - } - x := Foo{Number(`invalid`)} - - if _, err := Marshal(&x); err == nil { - t.Fatalf("Marshal error: got nil, want non-nil") - } -} - -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 _, err := Marshal(dummy); err == nil { - t.Errorf("Marshal error: got nil, want non-nil") - } - - type Data struct { - A string - I int - } - want := Data{A: "a", I: 1} - b, err := Marshal(want) - if err != nil { - t.Errorf("Marshal error: %v", err) - } - - var got Data - if err := Unmarshal(b, &got); err != nil { - t.Errorf("Unmarshal error: %v", err) - } - if got != want { - t.Errorf("Unmarshal:\n\tgot: %v\n\twant: %v", got, want) - } -} - -func TestHTMLEscape(t *testing.T) { - var b, want bytes.Buffer - m := `{"M":"foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `"}` - want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) - HTMLEscape(&b, []byte(m)) - if !bytes.Equal(b.Bytes(), want.Bytes()) { - t.Errorf("HTMLEscape:\n\tgot: %s\n\twant: %s", b.Bytes(), want.Bytes()) - } -} - -// 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 error: %v", err) - } - if got, want := string(b), `{"n":"42"}`; got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } - var back stringPointer - switch err = Unmarshal(b, &back); { - case err != nil: - t.Fatalf("Unmarshal error: %v", err) - case back.N == nil: - t.Fatalf("Unmarshal: back.N = nil, want non-nil") - case *back.N != 42: - t.Fatalf("Unmarshal: *back.N = %d, want 42", *back.N) - } -} - -var encodeStringTests = []struct { - in string - out string -}{ - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\b"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\f"`}, - {"\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) error: %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 - -func (b jsonbyte) MarshalJSON() ([]byte, error) { return tenc(`{"JB":%d}`, b) } - -type textbyte byte - -func (b textbyte) MarshalText() ([]byte, error) { return tenc(`TB:%d`, b) } - -type jsonint int - -func (i jsonint) MarshalJSON() ([]byte, error) { return tenc(`{"JI":%d}`, i) } - -type textint int - -func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } - -func tenc(format string, a ...any) ([]byte, error) { - var buf bytes.Buffer - fmt.Fprintf(&buf, format, a...) - return buf.Bytes(), nil -} - -type textfloat float64 - -func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) } - -// Issue 13783 -func TestEncodeBytekind(t *testing.T) { - tests := []struct { - CaseName - in any - want string - }{ - {Name(""), byte(7), "7"}, - {Name(""), jsonbyte(7), `{"JB":7}`}, - {Name(""), textbyte(4), `"TB:4"`}, - {Name(""), jsonint(5), `{"JI":5}`}, - {Name(""), textint(1), `"TI:1"`}, - {Name(""), []byte{0, 1}, `"AAE="`}, - {Name(""), []jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, - {Name(""), [][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, - {Name(""), []textbyte{2, 3}, `["TB:2","TB:3"]`}, - {Name(""), []jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, - {Name(""), []textint{9, 3}, `["TI:9","TI:3"]`}, - {Name(""), []int{9, 3}, `[9,3]`}, - {Name(""), []textfloat{12, 3}, `["TF:12.00","TF:3.00"]`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.in) - if err != nil { - t.Errorf("%s: Marshal error: %v", tt.Where, err) - } - got, want := string(b), tt.want - if got != want { - t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, got, want) - } - }) - } -} - -func TestTextMarshalerMapKeysAreSorted(t *testing.T) { - got, err := Marshal(map[unmarshalerText]int{ - {"x", "y"}: 1, - {"y", "x"}: 2, - {"a", "z"}: 3, - {"z", "a"}: 4, - }) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -// https://golang.org/issue/33675 -func TestNilMarshalerTextMapKey(t *testing.T) { - got, err := Marshal(map[*unmarshalerText]int{ - (*unmarshalerText)(nil): 1, - {"A", "B"}: 2, - }) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - const want = `{"":1,"A:B":2}` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, 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-9]`), // positive exponent must be signed - 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 - // below here for float32 only - re(`[1-9][0-9]{8}[1-9]`), // too many significant digits in integer - re(`[1-9][0-9.]{9}[1-9]`), // too many significant digits in decimal -} - -func TestMarshalFloat(t *testing.T) { - t.Parallel() - nfail := 0 - test := func(f float64, bits int) { - vf := any(f) - if bits == 32 { - f = float64(float32(f)) // round - vf = float32(f) - } - bout, err := Marshal(vf) - if err != nil { - t.Errorf("Marshal(%T(%g)) error: %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("ParseFloat(%q) error: %v", out, err) - nfail++ - return - } - if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0 - t.Errorf("ParseFloat(%q):\n\tgot: %g\n\twant: %g", out, float32(g), vf) - nfail++ - return - } - - bad := badFloatREs - if bits == 64 { - bad = bad[:len(bad)-2] - } - for _, re := range bad { - 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 RawMessage `json:",omitempty"` - } - T2 struct { - M *RawMessage `json:",omitempty"` - } - ) - - var ( - rawNil = RawMessage(nil) - rawEmpty = RawMessage([]byte{}) - rawText = RawMessage([]byte(`"foo"`)) - ) - - tests := []struct { - CaseName - in any - want string - ok bool - }{ - // Test with nil RawMessage. - {Name(""), rawNil, "null", true}, - {Name(""), &rawNil, "null", true}, - {Name(""), []any{rawNil}, "[null]", true}, - {Name(""), &[]any{rawNil}, "[null]", true}, - {Name(""), []any{&rawNil}, "[null]", true}, - {Name(""), &[]any{&rawNil}, "[null]", true}, - {Name(""), struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {Name(""), &struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {Name(""), struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {Name(""), &struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {Name(""), map[string]any{"M": rawNil}, `{"M":null}`, true}, - {Name(""), &map[string]any{"M": rawNil}, `{"M":null}`, true}, - {Name(""), map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {Name(""), &map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {Name(""), T1{rawNil}, "{}", true}, - {Name(""), T2{&rawNil}, `{"M":null}`, true}, - {Name(""), &T1{rawNil}, "{}", true}, - {Name(""), &T2{&rawNil}, `{"M":null}`, true}, - - // Test with empty, but non-nil, RawMessage. - {Name(""), rawEmpty, "", false}, - {Name(""), &rawEmpty, "", false}, - {Name(""), []any{rawEmpty}, "", false}, - {Name(""), &[]any{rawEmpty}, "", false}, - {Name(""), []any{&rawEmpty}, "", false}, - {Name(""), &[]any{&rawEmpty}, "", false}, - {Name(""), struct{ X RawMessage }{rawEmpty}, "", false}, - {Name(""), &struct{ X RawMessage }{rawEmpty}, "", false}, - {Name(""), struct{ X *RawMessage }{&rawEmpty}, "", false}, - {Name(""), &struct{ X *RawMessage }{&rawEmpty}, "", false}, - {Name(""), map[string]any{"nil": rawEmpty}, "", false}, - {Name(""), &map[string]any{"nil": rawEmpty}, "", false}, - {Name(""), map[string]any{"nil": &rawEmpty}, "", false}, - {Name(""), &map[string]any{"nil": &rawEmpty}, "", false}, - {Name(""), T1{rawEmpty}, "{}", true}, - {Name(""), T2{&rawEmpty}, "", false}, - {Name(""), &T1{rawEmpty}, "{}", true}, - {Name(""), &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 - {Name(""), rawText, `"foo"`, true}, // Issue6458 - {Name(""), &rawText, `"foo"`, true}, - {Name(""), []any{rawText}, `["foo"]`, true}, // Issue6458 - {Name(""), &[]any{rawText}, `["foo"]`, true}, // Issue6458 - {Name(""), []any{&rawText}, `["foo"]`, true}, - {Name(""), &[]any{&rawText}, `["foo"]`, true}, - {Name(""), struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), &struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, - {Name(""), struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {Name(""), &struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {Name(""), map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), &map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {Name(""), &map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {Name(""), T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), T2{&rawText}, `{"M":"foo"}`, true}, - {Name(""), &T1{rawText}, `{"M":"foo"}`, true}, - {Name(""), &T2{&rawText}, `{"M":"foo"}`, true}, - } - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.in) - if ok := (err == nil); ok != tt.ok { - if err != nil { - t.Errorf("%s: Marshal error: %v", tt.Where, err) - } else { - t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where) - } - } - if got := string(b); got != tt.want { - t.Errorf("%s: Marshal:\n\tinput: %#v\n\tgot: %s\n\twant: %s", tt.Where, tt.in, got, tt.want) - } - }) - } -} - -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") -} - -func TestMarshalUncommonFieldNames(t *testing.T) { - v := struct { - A0, À, Aβ int - }{} - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal error:", err) - } - want := `{"A0":0,"À":0,"Aβ":0}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestMarshalerError(t *testing.T) { - s := "test variable" - st := reflect.TypeOf(s) - const errText = "json: test error" - - tests := []struct { - CaseName - err *MarshalerError - want string - }{{ - Name(""), - &MarshalerError{st, fmt.Errorf(errText), ""}, - "json: error calling MarshalJSON for type " + st.String() + ": " + errText, - }, { - Name(""), - &MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}, - "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, - }} - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - got := tt.err.Error() - if got != tt.want { - t.Errorf("%s: Error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) - } - }) - } -} - -type marshaledValue string - -func (v marshaledValue) MarshalJSON() ([]byte, error) { - return []byte(v), nil -} - -func TestIssue63379(t *testing.T) { - for _, v := range []string{ - "[]<", - "[]>", - "[]&", - "[]\u2028", - "[]\u2029", - "{}<", - "{}>", - "{}&", - "{}\u2028", - "{}\u2029", - } { - _, err := Marshal(marshaledValue(v)) - if err == nil { - t.Errorf("expected error for %q", v) - } - } -} - -// Issue #73733: encoding/json used a WaitGroup to coordinate access to cache entries. -// Since WaitGroup.Wait is durably blocking, this caused apparent deadlocks when -// multiple bubbles called json.Marshal at the same time. -func TestSynctestMarshal(t *testing.T) { - var wg sync.WaitGroup - for range 5 { - wg.Go(func() { - synctest.Test(t, func(t *testing.T) { - _, err := Marshal([]string{}) - if err != nil { - t.Errorf("Marshal: %v", err) - } - }) - }) - } - wg.Wait() -} diff --git a/pkg/encoders/json/example_marshaling_test.go b/pkg/encoders/json/example_marshaling_test.go deleted file mode 100644 index 72f4cca..0000000 --- a/pkg/encoders/json/example_marshaling_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// 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. - -//go:build !goexperiment.jsonv2 - -package json_test - -import ( - "encoding/json" - "fmt" - "log" - "strings" -) - -type Animal int - -const ( - Unknown Animal = iota - Gopher - Zebra -) - -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: - *a = Unknown - case "gopher": - *a = Gopher - case "zebra": - *a = Zebra - } - - return nil -} - -func (a Animal) MarshalJSON() ([]byte, error) { - var s string - switch a { - default: - s = "unknown" - case Gopher: - s = "gopher" - case Zebra: - s = "zebra" - } - - return json.Marshal(s) -} - -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) - } - - census := make(map[Animal]int) - for _, animal := range zoo { - census[animal] += 1 - } - - fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n", - census[Gopher], census[Zebra], census[Unknown]) - - // Output: - // Zoo Census: - // * Gophers: 3 - // * Zebras: 2 - // * Unknown: 3 -} diff --git a/pkg/encoders/json/example_test.go b/pkg/encoders/json/example_test.go deleted file mode 100644 index 15c2538..0000000 --- a/pkg/encoders/json/example_test.go +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2011 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. - -//go:build !goexperiment.jsonv2 - -package json_test - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "log" - "os" - "strings" -) - -func ExampleMarshal() { - type ColorGroup struct { - ID int - Name string - Colors []string - } - group := ColorGroup{ - ID: 1, - Name: "Reds", - Colors: []string{"Crimson", "Red", "Ruby", "Maroon"}, - } - b, err := json.Marshal(group) - if err != nil { - fmt.Println("error:", err) - } - os.Stdout.Write(b) - // Output: - // {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]} -} - -func ExampleUnmarshal() { - var jsonBlob = []byte(`[ - {"Name": "Platypus", "Order": "Monotremata"}, - {"Name": "Quoll", "Order": "Dasyuromorphia"} -]`) - type Animal struct { - Name string - Order string - } - var animals []Animal - err := json.Unmarshal(jsonBlob, &animals) - if err != nil { - fmt.Println("error:", err) - } - fmt.Printf("%+v", animals) - // Output: - // [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}] -} - -// This example uses a Decoder to decode a stream of distinct JSON values. -func ExampleDecoder() { - const jsonStream = ` - {"Name": "Ed", "Text": "Knock knock."} - {"Name": "Sam", "Text": "Who's there?"} - {"Name": "Ed", "Text": "Go fmt."} - {"Name": "Sam", "Text": "Go fmt who?"} - {"Name": "Ed", "Text": "Go fmt yourself!"} -` - type Message struct { - Name, Text string - } - dec := json.NewDecoder(strings.NewReader(jsonStream)) - for { - var m Message - if err := dec.Decode(&m); err == io.EOF { - break - } else if err != nil { - log.Fatal(err) - } - fmt.Printf("%s: %s\n", m.Name, m.Text) - } - // Output: - // Ed: Knock knock. - // Sam: Who's there? - // Ed: Go fmt. - // Sam: Go fmt who? - // Ed: Go fmt yourself! -} - -// This example uses a Decoder to decode a stream of distinct JSON values. -func ExampleDecoder_Token() { - const jsonStream = ` - {"Message": "Hello", "Array": [1, 2, 3], "Null": null, "Number": 1.234} -` - dec := json.NewDecoder(strings.NewReader(jsonStream)) - for { - t, err := dec.Token() - if err == io.EOF { - break - } - if err != nil { - log.Fatal(err) - } - fmt.Printf("%T: %v", t, t) - if dec.More() { - fmt.Printf(" (more)") - } - fmt.Printf("\n") - } - // Output: - // json.Delim: { (more) - // string: Message (more) - // string: Hello (more) - // string: Array (more) - // json.Delim: [ (more) - // float64: 1 (more) - // float64: 2 (more) - // float64: 3 - // json.Delim: ] (more) - // string: Null (more) - // : (more) - // string: Number (more) - // float64: 1.234 - // json.Delim: } -} - -// This example uses a Decoder to decode a streaming array of JSON objects. -func ExampleDecoder_Decode_stream() { - const jsonStream = ` - [ - {"Name": "Ed", "Text": "Knock knock."}, - {"Name": "Sam", "Text": "Who's there?"}, - {"Name": "Ed", "Text": "Go fmt."}, - {"Name": "Sam", "Text": "Go fmt who?"}, - {"Name": "Ed", "Text": "Go fmt yourself!"} - ] -` - type Message struct { - Name, Text string - } - dec := json.NewDecoder(strings.NewReader(jsonStream)) - - // read open bracket - t, err := dec.Token() - if err != nil { - log.Fatal(err) - } - fmt.Printf("%T: %v\n", t, t) - - // while the array contains values - for dec.More() { - var m Message - // decode an array value (Message) - err := dec.Decode(&m) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%v: %v\n", m.Name, m.Text) - } - - // read closing bracket - t, err = dec.Token() - if err != nil { - log.Fatal(err) - } - fmt.Printf("%T: %v\n", t, t) - - // Output: - // json.Delim: [ - // Ed: Knock knock. - // Sam: Who's there? - // Ed: Go fmt. - // Sam: Go fmt who? - // Ed: Go fmt yourself! - // json.Delim: ] -} - -// This example uses RawMessage to delay parsing part of a JSON message. -func ExampleRawMessage_unmarshal() { - type Color struct { - Space string - Point json.RawMessage // delay parsing until we know the color space - } - type RGB struct { - R uint8 - G uint8 - B uint8 - } - type YCbCr struct { - Y uint8 - Cb int8 - Cr int8 - } - - var j = []byte(`[ - {"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}}, - {"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}} -]`) - var colors []Color - err := json.Unmarshal(j, &colors) - if err != nil { - log.Fatalln("error:", err) - } - - for _, c := range colors { - var dst any - switch c.Space { - case "RGB": - dst = new(RGB) - case "YCbCr": - dst = new(YCbCr) - } - err := json.Unmarshal(c.Point, dst) - if err != nil { - log.Fatalln("error:", err) - } - fmt.Println(c.Space, dst) - } - // Output: - // YCbCr &{255 0 -10} - // RGB &{98 218 255} -} - -// This example uses RawMessage to use a precomputed JSON during marshal. -func ExampleRawMessage_marshal() { - h := json.RawMessage(`{"precomputed": true}`) - - c := struct { - Header *json.RawMessage `json:"header"` - Body string `json:"body"` - }{Header: &h, Body: "Hello Gophers!"} - - b, err := json.MarshalIndent(&c, "", "\t") - if err != nil { - fmt.Println("error:", err) - } - os.Stdout.Write(b) - - // Output: - // { - // "header": { - // "precomputed": true - // }, - // "body": "Hello Gophers!" - // } -} - -func ExampleIndent() { - type Road struct { - Name string - Number int - } - roads := []Road{ - {"Diamond Fork", 29}, - {"Sheep Creek", 51}, - } - - b, err := json.Marshal(roads) - if err != nil { - log.Fatal(err) - } - - var out bytes.Buffer - json.Indent(&out, b, "=", "\t") - out.WriteTo(os.Stdout) - // Output: - // [ - // = { - // = "Name": "Diamond Fork", - // = "Number": 29 - // = }, - // = { - // = "Name": "Sheep Creek", - // = "Number": 51 - // = } - // =] -} - -func ExampleMarshalIndent() { - data := map[string]int{ - "a": 1, - "b": 2, - } - - b, err := json.MarshalIndent(data, "", "") - if err != nil { - log.Fatal(err) - } - - fmt.Println(string(b)) - // Output: - // { - // "a": 1, - // "b": 2 - // } -} - -func ExampleValid() { - goodJSON := `{"example": 1}` - badJSON := `{"example":2:]}}` - - fmt.Println(json.Valid([]byte(goodJSON)), json.Valid([]byte(badJSON))) - // Output: - // true false -} - -func ExampleHTMLEscape() { - var out bytes.Buffer - json.HTMLEscape(&out, []byte(`{"Name":"HTML content"}`)) - out.WriteTo(os.Stdout) - // Output: - //{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"} -} diff --git a/pkg/encoders/json/example_text_marshaling_test.go b/pkg/encoders/json/example_text_marshaling_test.go deleted file mode 100644 index 178c7ba..0000000 --- a/pkg/encoders/json/example_text_marshaling_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// 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. - -//go:build !goexperiment.jsonv2 - -package json_test - -import ( - "encoding/json" - "fmt" - "log" - "strings" -) - -type Size int - -const ( - Unrecognized Size = iota - Small - Large -) - -func (s *Size) UnmarshalText(text []byte) error { - switch strings.ToLower(string(text)) { - default: - *s = Unrecognized - case "small": - *s = Small - case "large": - *s = Large - } - return nil -} - -func (s Size) MarshalText() ([]byte, error) { - var name string - switch s { - default: - name = "unrecognized" - case Small: - name = "small" - case Large: - name = "large" - } - return []byte(name), 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) - } - - counts := make(map[Size]int) - for _, size := range inventory { - counts[size] += 1 - } - - fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n", - counts[Small], counts[Large], counts[Unrecognized]) - - // Output: - // Inventory Counts: - // * Small: 3 - // * Large: 2 - // * Unrecognized: 3 -} diff --git a/pkg/encoders/json/fold_test.go b/pkg/encoders/json/fold_test.go deleted file mode 100644 index 4d03e3d..0000000 --- a/pkg/encoders/json/fold_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2013 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. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "bytes" - "testing" -) - -func FuzzEqualFold(f *testing.F) { - for _, ss := range [][2]string{ - {"", ""}, - {"123abc", "123ABC"}, - {"αβδ", "ΑΒΔ"}, - {"abc", "xyz"}, - {"abc", "XYZ"}, - {"1", "2"}, - {"hello, world!", "hello, world!"}, - {"hello, world!", "Hello, World!"}, - {"hello, world!", "HELLO, WORLD!"}, - {"hello, world!", "jello, world!"}, - {"γειά, κόσμε!", "γειά, κόσμε!"}, - {"γειά, κόσμε!", "Γειά, Κόσμε!"}, - {"γειά, κόσμε!", "ΓΕΙΆ, ΚΌΣΜΕ!"}, - {"γειά, κόσμε!", "ΛΕΙΆ, ΚΌΣΜΕ!"}, - {"AESKey", "aesKey"}, - {"AESKEY", "aes_key"}, - {"aes_key", "AES_KEY"}, - {"AES_KEY", "aes-key"}, - {"aes-key", "AES-KEY"}, - {"AES-KEY", "aesKey"}, - {"aesKey", "AesKey"}, - {"AesKey", "AESKey"}, - {"AESKey", "aeskey"}, - {"DESKey", "aeskey"}, - {"AES Key", "aeskey"}, - } { - f.Add([]byte(ss[0]), []byte(ss[1])) - } - equalFold := func(x, y []byte) bool { return string(foldName(x)) == string(foldName(y)) } - f.Fuzz(func(t *testing.T, x, y []byte) { - got := equalFold(x, y) - want := bytes.EqualFold(x, y) - if got != want { - t.Errorf("equalFold(%q, %q) = %v, want %v", x, y, got, want) - } - }) -} diff --git a/pkg/encoders/json/fuzz_test.go b/pkg/encoders/json/fuzz_test.go deleted file mode 100644 index 37dc436..0000000 --- a/pkg/encoders/json/fuzz_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// 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. - -//go:build !goexperiment.jsonv2 - -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() any{ - func() any { return new(any) }, - func() any { return new(map[string]any) }, - func() any { return new([]any) }, - } { - 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 - } - } - }) -} diff --git a/pkg/encoders/json/internal/jsonflags/flags_test.go b/pkg/encoders/json/internal/jsonflags/flags_test.go deleted file mode 100644 index e4d3358..0000000 --- a/pkg/encoders/json/internal/jsonflags/flags_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2023 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. - -//go:build goexperiment.jsonv2 - -package jsonflags - -import "testing" - -func TestFlags(t *testing.T) { - type Check struct{ want Flags } - type Join struct{ in Flags } - type Set struct{ in Bools } - type Clear struct{ in Bools } - type Get struct { - in Bools - want bool - wantOk bool - } - - calls := []any{ - Get{in: AllowDuplicateNames, want: false, wantOk: false}, - Set{in: AllowDuplicateNames | 0}, - Get{in: AllowDuplicateNames, want: false, wantOk: true}, - Set{in: AllowDuplicateNames | 1}, - Get{in: AllowDuplicateNames, want: true, wantOk: true}, - Check{want: Flags{Presence: uint64(AllowDuplicateNames), Values: uint64(AllowDuplicateNames)}}, - Get{in: AllowInvalidUTF8, want: false, wantOk: false}, - Set{in: AllowInvalidUTF8 | 1}, - Get{in: AllowInvalidUTF8, want: true, wantOk: true}, - Set{in: AllowInvalidUTF8 | 0}, - Get{in: AllowInvalidUTF8, want: false, wantOk: true}, - Get{in: AllowDuplicateNames, want: true, wantOk: true}, - Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}}, - Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 0}, - Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(0)}}, - Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 0}, - Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(0)}}, - Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 1}, - Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames | AllowInvalidUTF8)}}, - Join{in: Flags{Presence: 0, Values: 0}}, - Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames | AllowInvalidUTF8)}}, - Join{in: Flags{Presence: uint64(Multiline | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}}, - Check{want: Flags{Presence: uint64(Multiline | AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}}, - Clear{in: AllowDuplicateNames | AllowInvalidUTF8}, - Check{want: Flags{Presence: uint64(Multiline), Values: uint64(0)}}, - Set{in: AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | 1}, - Set{in: Multiline | StringifyNumbers | 0}, - Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | Multiline | StringifyNumbers), Values: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics)}}, - Clear{in: ^AllCoderFlags}, - Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Multiline), Values: uint64(AllowInvalidUTF8)}}, - } - var fs Flags - for i, call := range calls { - switch call := call.(type) { - case Join: - fs.Join(call.in) - case Set: - fs.Set(call.in) - case Clear: - fs.Clear(call.in) - case Get: - got := fs.Get(call.in) - gotOk := fs.Has(call.in) - if got != call.want || gotOk != call.wantOk { - t.Fatalf("%d: GetOk = (%v, %v), want (%v, %v)", i, got, gotOk, call.want, call.wantOk) - } - case Check: - if fs != call.want { - t.Fatalf("%d: got %x, want %x", i, fs, call.want) - } - } - } -} diff --git a/pkg/encoders/json/internal/jsonopts/options_test.go b/pkg/encoders/json/internal/jsonopts/options_test.go deleted file mode 100644 index ebfaf05..0000000 --- a/pkg/encoders/json/internal/jsonopts/options_test.go +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2023 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. - -//go:build goexperiment.jsonv2 - -package jsonopts_test - -import ( - "reflect" - "testing" - - "encoding/json/internal/jsonflags" - . "encoding/json/internal/jsonopts" - "encoding/json/jsontext" - "encoding/json/v2" -) - -func makeFlags(f ...jsonflags.Bools) (fs jsonflags.Flags) { - for _, f := range f { - fs.Set(f) - } - return fs -} - -func TestJoin(t *testing.T) { - tests := []struct { - in Options - excludeCoders bool - want *Struct - }{{ - in: jsonflags.AllowInvalidUTF8 | 1, - want: &Struct{Flags: makeFlags(jsonflags.AllowInvalidUTF8 | 1)}, - }, { - in: jsonflags.Multiline | 0, - want: &Struct{ - Flags: makeFlags(jsonflags.AllowInvalidUTF8|1, jsonflags.Multiline|0)}, - }, { - in: Indent("\t"), // implicitly sets Multiline=true - want: &Struct{ - Flags: makeFlags(jsonflags.AllowInvalidUTF8 | jsonflags.Multiline | jsonflags.Indent | 1), - CoderValues: CoderValues{Indent: "\t"}, - }, - }, { - in: &Struct{ - Flags: makeFlags(jsonflags.Multiline|jsonflags.EscapeForJS|0, jsonflags.AllowInvalidUTF8|1), - }, - want: &Struct{ - Flags: makeFlags(jsonflags.AllowInvalidUTF8|jsonflags.Indent|1, jsonflags.Multiline|jsonflags.EscapeForJS|0), - CoderValues: CoderValues{Indent: "\t"}, - }, - }, { - in: &DefaultOptionsV1, - want: func() *Struct { - v1 := DefaultOptionsV1 - v1.Flags.Set(jsonflags.Indent | 1) - v1.Flags.Set(jsonflags.Multiline | 0) - v1.Indent = "\t" - return &v1 - }(), // v1 fully replaces before (except for whitespace related flags) - }, { - in: &DefaultOptionsV2, - want: func() *Struct { - v2 := DefaultOptionsV2 - v2.Flags.Set(jsonflags.Indent | 1) - v2.Flags.Set(jsonflags.Multiline | 0) - v2.Indent = "\t" - return &v2 - }(), // v2 fully replaces before (except for whitespace related flags) - }, { - in: jsonflags.Deterministic | jsonflags.AllowInvalidUTF8 | 1, excludeCoders: true, - want: func() *Struct { - v2 := DefaultOptionsV2 - v2.Flags.Set(jsonflags.Deterministic | 1) - v2.Flags.Set(jsonflags.Indent | 1) - v2.Flags.Set(jsonflags.Multiline | 0) - v2.Indent = "\t" - return &v2 - }(), - }, { - in: jsontext.WithIndentPrefix(" "), excludeCoders: true, - want: func() *Struct { - v2 := DefaultOptionsV2 - v2.Flags.Set(jsonflags.Deterministic | 1) - v2.Flags.Set(jsonflags.Indent | 1) - v2.Flags.Set(jsonflags.Multiline | 0) - v2.Indent = "\t" - return &v2 - }(), - }, { - in: jsontext.WithIndentPrefix(" "), excludeCoders: false, - want: func() *Struct { - v2 := DefaultOptionsV2 - v2.Flags.Set(jsonflags.Deterministic | 1) - v2.Flags.Set(jsonflags.Indent | 1) - v2.Flags.Set(jsonflags.IndentPrefix | 1) - v2.Flags.Set(jsonflags.Multiline | 1) - v2.Indent = "\t" - v2.IndentPrefix = " " - return &v2 - }(), - }, { - in: &Struct{ - Flags: jsonflags.Flags{ - Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix), - Values: uint64(jsonflags.Indent | jsonflags.IndentPrefix), - }, - CoderValues: CoderValues{Indent: " ", IndentPrefix: " "}, - }, - excludeCoders: true, - want: func() *Struct { - v2 := DefaultOptionsV2 - v2.Flags.Set(jsonflags.Indent | 1) - v2.Flags.Set(jsonflags.IndentPrefix | 1) - v2.Flags.Set(jsonflags.Multiline | 1) - v2.Indent = "\t" - v2.IndentPrefix = " " - return &v2 - }(), - }, { - in: &Struct{ - Flags: jsonflags.Flags{ - Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix), - Values: uint64(jsonflags.Indent | jsonflags.IndentPrefix), - }, - CoderValues: CoderValues{Indent: " ", IndentPrefix: " "}, - }, - excludeCoders: false, - want: func() *Struct { - v2 := DefaultOptionsV2 - v2.Flags.Set(jsonflags.Indent | 1) - v2.Flags.Set(jsonflags.IndentPrefix | 1) - v2.Flags.Set(jsonflags.Multiline | 1) - v2.Indent = " " - v2.IndentPrefix = " " - return &v2 - }(), - }} - got := new(Struct) - for i, tt := range tests { - if tt.excludeCoders { - got.JoinWithoutCoderOptions(tt.in) - } else { - got.Join(tt.in) - } - if !reflect.DeepEqual(got, tt.want) { - t.Fatalf("%d: Join:\n\tgot: %+v\n\twant: %+v", i, got, tt.want) - } - } -} - -func TestGet(t *testing.T) { - opts := &Struct{ - Flags: makeFlags(jsonflags.Indent|jsonflags.Deterministic|jsonflags.Marshalers|1, jsonflags.Multiline|0), - CoderValues: CoderValues{Indent: "\t"}, - ArshalValues: ArshalValues{Marshalers: new(json.Marshalers)}, - } - if v, ok := json.GetOption(nil, jsontext.AllowDuplicateNames); v || ok { - t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) - } - if v, ok := json.GetOption(jsonflags.AllowInvalidUTF8|0, jsontext.AllowDuplicateNames); v || ok { - t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) - } - if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|0, jsontext.AllowDuplicateNames); v || !ok { - t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, true)", v, ok) - } - if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.AllowDuplicateNames); !v || !ok { - t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (true, true)", v, ok) - } - if v, ok := json.GetOption(Indent(""), jsontext.AllowDuplicateNames); v || ok { - t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) - } - if v, ok := json.GetOption(Indent(" "), jsontext.WithIndent); v != " " || !ok { - t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want (" ", true)`, v, ok) - } - if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.WithIndent); v != "" || ok { - t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("", false)`, v, ok) - } - if v, ok := json.GetOption(opts, jsontext.AllowDuplicateNames); v || ok { - t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) - } - if v, ok := json.GetOption(opts, json.Deterministic); !v || !ok { - t.Errorf("GetOption(..., Deterministic) = (%v, %v), want (true, true)", v, ok) - } - if v, ok := json.GetOption(opts, jsontext.Multiline); v || !ok { - t.Errorf("GetOption(..., Multiline) = (%v, %v), want (false, true)", v, ok) - } - if v, ok := json.GetOption(opts, jsontext.AllowInvalidUTF8); v || ok { - t.Errorf("GetOption(..., AllowInvalidUTF8) = (%v, %v), want (false, false)", v, ok) - } - if v, ok := json.GetOption(opts, jsontext.WithIndent); v != "\t" || !ok { - t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("\t", true)`, v, ok) - } - if v, ok := json.GetOption(opts, jsontext.WithIndentPrefix); v != "" || ok { - t.Errorf(`GetOption(..., WithIndentPrefix) = (%q, %v), want ("", false)`, v, ok) - } - if v, ok := json.GetOption(opts, json.WithMarshalers); v == nil || !ok { - t.Errorf(`GetOption(..., WithMarshalers) = (%v, %v), want (non-nil, true)`, v, ok) - } - if v, ok := json.GetOption(opts, json.WithUnmarshalers); v != nil || ok { - t.Errorf(`GetOption(..., WithUnmarshalers) = (%v, %v), want (nil, false)`, v, ok) - } -} - -var sink struct { - Bool bool - String string - Marshalers *json.Marshalers -} - -func BenchmarkGetBool(b *testing.B) { - b.ReportAllocs() - opts := json.DefaultOptionsV2() - for range b.N { - sink.Bool, sink.Bool = json.GetOption(opts, jsontext.AllowDuplicateNames) - } -} - -func BenchmarkGetIndent(b *testing.B) { - b.ReportAllocs() - opts := json.DefaultOptionsV2() - for range b.N { - sink.String, sink.Bool = json.GetOption(opts, jsontext.WithIndent) - } -} - -func BenchmarkGetMarshalers(b *testing.B) { - b.ReportAllocs() - opts := json.JoinOptions(json.DefaultOptionsV2(), json.WithMarshalers(nil)) - for range b.N { - sink.Marshalers, sink.Bool = json.GetOption(opts, json.WithMarshalers) - } -} diff --git a/pkg/encoders/json/internal/jsonwire/decode_test.go b/pkg/encoders/json/internal/jsonwire/decode_test.go deleted file mode 100644 index 549c1a1..0000000 --- a/pkg/encoders/json/internal/jsonwire/decode_test.go +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright 2023 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. - -//go:build goexperiment.jsonv2 - -package jsonwire - -import ( - "errors" - "io" - "math" - "reflect" - "strings" - "testing" -) - -func TestConsumeWhitespace(t *testing.T) { - tests := []struct { - in string - want int - }{ - {"", 0}, - {"a", 0}, - {" a", 1}, - {" a ", 1}, - {" \n\r\ta", 4}, - {" \n\r\t \n\r\t \n\r\t \n\r\t", 16}, - {"\u00a0", 0}, // non-breaking space is not JSON whitespace - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - if got := ConsumeWhitespace([]byte(tt.in)); got != tt.want { - t.Errorf("ConsumeWhitespace(%q) = %v, want %v", tt.in, got, tt.want) - } - }) - } -} - -func TestConsumeLiteral(t *testing.T) { - tests := []struct { - literal string - in string - want int - wantErr error - }{ - {"null", "", 0, io.ErrUnexpectedEOF}, - {"null", "n", 1, io.ErrUnexpectedEOF}, - {"null", "nu", 2, io.ErrUnexpectedEOF}, - {"null", "nul", 3, io.ErrUnexpectedEOF}, - {"null", "null", 4, nil}, - {"null", "nullx", 4, nil}, - {"null", "x", 0, NewInvalidCharacterError("x", "in literal null (expecting 'n')")}, - {"null", "nuxx", 2, NewInvalidCharacterError("x", "in literal null (expecting 'l')")}, - - {"false", "", 0, io.ErrUnexpectedEOF}, - {"false", "f", 1, io.ErrUnexpectedEOF}, - {"false", "fa", 2, io.ErrUnexpectedEOF}, - {"false", "fal", 3, io.ErrUnexpectedEOF}, - {"false", "fals", 4, io.ErrUnexpectedEOF}, - {"false", "false", 5, nil}, - {"false", "falsex", 5, nil}, - {"false", "x", 0, NewInvalidCharacterError("x", "in literal false (expecting 'f')")}, - {"false", "falsx", 4, NewInvalidCharacterError("x", "in literal false (expecting 'e')")}, - - {"true", "", 0, io.ErrUnexpectedEOF}, - {"true", "t", 1, io.ErrUnexpectedEOF}, - {"true", "tr", 2, io.ErrUnexpectedEOF}, - {"true", "tru", 3, io.ErrUnexpectedEOF}, - {"true", "true", 4, nil}, - {"true", "truex", 4, nil}, - {"true", "x", 0, NewInvalidCharacterError("x", "in literal true (expecting 't')")}, - {"true", "trux", 3, NewInvalidCharacterError("x", "in literal true (expecting 'e')")}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - var got int - switch tt.literal { - case "null": - got = ConsumeNull([]byte(tt.in)) - case "false": - got = ConsumeFalse([]byte(tt.in)) - case "true": - got = ConsumeTrue([]byte(tt.in)) - default: - t.Errorf("invalid literal: %v", tt.literal) - } - switch { - case tt.wantErr == nil && got != tt.want: - t.Errorf("Consume%v(%q) = %v, want %v", strings.Title(tt.literal), tt.in, got, tt.want) - case tt.wantErr != nil && got != 0: - t.Errorf("Consume%v(%q) = %v, want %v", strings.Title(tt.literal), tt.in, got, 0) - } - - got, gotErr := ConsumeLiteral([]byte(tt.in), tt.literal) - if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { - t.Errorf("ConsumeLiteral(%q, %q) = (%v, %v), want (%v, %v)", tt.in, tt.literal, got, gotErr, tt.want, tt.wantErr) - } - }) - } -} - -func TestConsumeString(t *testing.T) { - var errPrev = errors.New("same as previous error") - tests := []struct { - in string - simple bool - want int - wantUTF8 int // consumed bytes if validateUTF8 is specified - wantFlags ValueFlags - wantUnquote string - wantErr error - wantErrUTF8 error // error if validateUTF8 is specified - wantErrUnquote error - }{ - {``, false, 0, 0, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"`, false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`""`, true, 2, 2, 0, "", nil, nil, nil}, - {`""x`, true, 2, 2, 0, "", nil, nil, NewInvalidCharacterError("x", "after string value")}, - {` ""x`, false, 0, 0, 0, "", NewInvalidCharacterError(" ", "at start of string (expecting '\"')"), errPrev, errPrev}, - {`"hello`, false, 6, 6, 0, "hello", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"hello"`, true, 7, 7, 0, "hello", nil, nil, nil}, - {"\"\x00\"", false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidCharacterError("\x00", "in string (expecting non-control character)"), errPrev, errPrev}, - {`"\u0000"`, false, 8, 8, stringNonVerbatim, "\x00", nil, nil, nil}, - {"\"\x1f\"", false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidCharacterError("\x1f", "in string (expecting non-control character)"), errPrev, errPrev}, - {`"\u001f"`, false, 8, 8, stringNonVerbatim, "\x1f", nil, nil, nil}, - {`"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, true, 54, 54, 0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", nil, nil, nil}, - {"\" !#$%'()*+,-./0123456789:;=?@[]^_`{|}~\x7f\"", true, 41, 41, 0, " !#$%'()*+,-./0123456789:;=?@[]^_`{|}~\x7f", nil, nil, nil}, - {`"&"`, false, 3, 3, 0, "&", nil, nil, nil}, - {`"<"`, false, 3, 3, 0, "<", nil, nil, nil}, - {`">"`, false, 3, 3, 0, ">", nil, nil, nil}, - {"\"x\x80\"", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", nil, ErrInvalidUTF8, errPrev}, - {"\"x\xff\"", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", nil, ErrInvalidUTF8, errPrev}, - {"\"x\xc0", false, 3, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, - {"\"x\xc0\x80\"", false, 5, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, - {"\"x\xe0", false, 2, 2, 0, "x", io.ErrUnexpectedEOF, errPrev, errPrev}, - {"\"x\xe0\x80", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, - {"\"x\xe0\x80\x80\"", false, 6, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, - {"\"x\xf0", false, 2, 2, 0, "x", io.ErrUnexpectedEOF, errPrev, errPrev}, - {"\"x\xf0\x80", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, - {"\"x\xf0\x80\x80", false, 5, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, - {"\"x\xf0\x80\x80\x80\"", false, 7, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, - {"\"x\xed\xba\xad\"", false, 6, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, - {"\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", false, 25, 25, 0, "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", nil, nil, nil}, - {`"¢"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"¢"`[:3], false, 3, 3, 0, "¢", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote - {`"¢"`[:4], false, 4, 4, 0, "¢", nil, nil, nil}, - {`"€"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"€"`[:3], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"€"`[:4], false, 4, 4, 0, "€", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote - {`"€"`[:5], false, 5, 5, 0, "€", nil, nil, nil}, - {`"𐍈"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"𐍈"`[:3], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"𐍈"`[:4], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"𐍈"`[:5], false, 5, 5, 0, "𐍈", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote - {`"𐍈"`[:6], false, 6, 6, 0, "𐍈", nil, nil, nil}, - {`"x\`, false, 2, 2, stringNonVerbatim, "x", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"x\"`, false, 4, 4, stringNonVerbatim, "x\"", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"x\x"`, false, 2, 2, stringNonVerbatim | stringNonCanonical, "x", NewInvalidEscapeSequenceError(`\x`), errPrev, errPrev}, - {`"\"\\\b\f\n\r\t"`, false, 16, 16, stringNonVerbatim, "\"\\\b\f\n\r\t", nil, nil, nil}, - {`"/"`, true, 3, 3, 0, "/", nil, nil, nil}, - {`"\/"`, false, 4, 4, stringNonVerbatim | stringNonCanonical, "/", nil, nil, nil}, - {`"\u002f"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "/", nil, nil, nil}, - {`"\u`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"\uf`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"\uff`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"\ufff`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"\ufffd`, false, 7, 7, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"\ufffd"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "\ufffd", nil, nil, nil}, - {`"\uABCD"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "\uabcd", nil, nil, nil}, - {`"\uefX0"`, false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidEscapeSequenceError(`\uefX0`), errPrev, errPrev}, - {`"\uDEAD`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"\uDEAD"`, false, 8, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", nil, NewInvalidEscapeSequenceError(`\uDEAD"`), errPrev}, - {`"\uDEAD______"`, false, 14, 1, stringNonVerbatim | stringNonCanonical, "\ufffd______", nil, NewInvalidEscapeSequenceError(`\uDEAD______`), errPrev}, - {`"\uDEAD\uXXXX"`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", NewInvalidEscapeSequenceError(`\uXXXX`), NewInvalidEscapeSequenceError(`\uDEAD\uXXXX`), NewInvalidEscapeSequenceError(`\uXXXX`)}, - {`"\uDEAD\uBEEF"`, false, 14, 1, stringNonVerbatim | stringNonCanonical, "\ufffd\ubeef", nil, NewInvalidEscapeSequenceError(`\uDEAD\uBEEF`), errPrev}, - {`"\uD800\udea`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev}, - {`"\uD800\udb`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, NewInvalidEscapeSequenceError(`\uD800\udb`), io.ErrUnexpectedEOF}, - {`"\uD800\udead"`, false, 14, 14, stringNonVerbatim | stringNonCanonical, "\U000102ad", nil, nil, nil}, - {`"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, false, 50, 50, stringNonVerbatim | stringNonCanonical, "\"\\/\b\f\n\r\t", nil, nil, nil}, - {`"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`, false, 56, 56, stringNonVerbatim | stringNonCanonical, "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", nil, nil, nil}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - if tt.wantErrUTF8 == errPrev { - tt.wantErrUTF8 = tt.wantErr - } - if tt.wantErrUnquote == errPrev { - tt.wantErrUnquote = tt.wantErrUTF8 - } - - switch got := ConsumeSimpleString([]byte(tt.in)); { - case tt.simple && got != tt.want: - t.Errorf("consumeSimpleString(%q) = %v, want %v", tt.in, got, tt.want) - case !tt.simple && got != 0: - t.Errorf("consumeSimpleString(%q) = %v, want %v", tt.in, got, 0) - } - - var gotFlags ValueFlags - got, gotErr := ConsumeString(&gotFlags, []byte(tt.in), false) - if gotFlags != tt.wantFlags { - t.Errorf("consumeString(%q, false) flags = %v, want %v", tt.in, gotFlags, tt.wantFlags) - } - if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { - t.Errorf("consumeString(%q, false) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) - } - - got, gotErr = ConsumeString(&gotFlags, []byte(tt.in), true) - if got != tt.wantUTF8 || !reflect.DeepEqual(gotErr, tt.wantErrUTF8) { - t.Errorf("consumeString(%q, false) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.wantUTF8, tt.wantErrUTF8) - } - - gotUnquote, gotErr := AppendUnquote(nil, tt.in) - if string(gotUnquote) != tt.wantUnquote || !reflect.DeepEqual(gotErr, tt.wantErrUnquote) { - t.Errorf("AppendUnquote(nil, %q) = (%q, %v), want (%q, %v)", tt.in[:got], gotUnquote, gotErr, tt.wantUnquote, tt.wantErrUnquote) - } - }) - } -} - -func TestConsumeNumber(t *testing.T) { - tests := []struct { - in string - simple bool - want int - wantErr error - }{ - {"", false, 0, io.ErrUnexpectedEOF}, - {`"NaN"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")}, - {`"Infinity"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")}, - {`"-Infinity"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")}, - {".0", false, 0, NewInvalidCharacterError(".", "in number (expecting digit)")}, - {"0", true, 1, nil}, - {"-0", false, 2, nil}, - {"+0", false, 0, NewInvalidCharacterError("+", "in number (expecting digit)")}, - {"1", true, 1, nil}, - {"-1", false, 2, nil}, - {"00", true, 1, nil}, - {"-00", false, 2, nil}, - {"01", true, 1, nil}, - {"-01", false, 2, nil}, - {"0i", true, 1, nil}, - {"-0i", false, 2, nil}, - {"0f", true, 1, nil}, - {"-0f", false, 2, nil}, - {"9876543210", true, 10, nil}, - {"-9876543210", false, 11, nil}, - {"9876543210x", true, 10, nil}, - {"-9876543210x", false, 11, nil}, - {" 9876543210", true, 0, NewInvalidCharacterError(" ", "in number (expecting digit)")}, - {"- 9876543210", false, 1, NewInvalidCharacterError(" ", "in number (expecting digit)")}, - {strings.Repeat("9876543210", 1000), true, 10000, nil}, - {"-" + strings.Repeat("9876543210", 1000), false, 1 + 10000, nil}, - {"0.", false, 1, io.ErrUnexpectedEOF}, - {"-0.", false, 2, io.ErrUnexpectedEOF}, - {"0e", false, 1, io.ErrUnexpectedEOF}, - {"-0e", false, 2, io.ErrUnexpectedEOF}, - {"0E", false, 1, io.ErrUnexpectedEOF}, - {"-0E", false, 2, io.ErrUnexpectedEOF}, - {"0.0", false, 3, nil}, - {"-0.0", false, 4, nil}, - {"0e0", false, 3, nil}, - {"-0e0", false, 4, nil}, - {"0E0", false, 3, nil}, - {"-0E0", false, 4, nil}, - {"0.0123456789", false, 12, nil}, - {"-0.0123456789", false, 13, nil}, - {"1.f", false, 2, NewInvalidCharacterError("f", "in number (expecting digit)")}, - {"-1.f", false, 3, NewInvalidCharacterError("f", "in number (expecting digit)")}, - {"1.e", false, 2, NewInvalidCharacterError("e", "in number (expecting digit)")}, - {"-1.e", false, 3, NewInvalidCharacterError("e", "in number (expecting digit)")}, - {"1e0", false, 3, nil}, - {"-1e0", false, 4, nil}, - {"1E0", false, 3, nil}, - {"-1E0", false, 4, nil}, - {"1Ex", false, 2, NewInvalidCharacterError("x", "in number (expecting digit)")}, - {"-1Ex", false, 3, NewInvalidCharacterError("x", "in number (expecting digit)")}, - {"1e-0", false, 4, nil}, - {"-1e-0", false, 5, nil}, - {"1e+0", false, 4, nil}, - {"-1e+0", false, 5, nil}, - {"1E-0", false, 4, nil}, - {"-1E-0", false, 5, nil}, - {"1E+0", false, 4, nil}, - {"-1E+0", false, 5, nil}, - {"1E+00500", false, 8, nil}, - {"-1E+00500", false, 9, nil}, - {"1E+00500x", false, 8, nil}, - {"-1E+00500x", false, 9, nil}, - {"9876543210.0123456789e+01234589x", false, 31, nil}, - {"-9876543210.0123456789e+01234589x", false, 32, nil}, - {"1_000_000", true, 1, nil}, - {"0x12ef", true, 1, nil}, - {"0x1p-2", true, 1, nil}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - switch got := ConsumeSimpleNumber([]byte(tt.in)); { - case tt.simple && got != tt.want: - t.Errorf("ConsumeSimpleNumber(%q) = %v, want %v", tt.in, got, tt.want) - case !tt.simple && got != 0: - t.Errorf("ConsumeSimpleNumber(%q) = %v, want %v", tt.in, got, 0) - } - - got, gotErr := ConsumeNumber([]byte(tt.in)) - if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { - t.Errorf("ConsumeNumber(%q) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) - } - }) - } -} - -func TestParseHexUint16(t *testing.T) { - tests := []struct { - in string - want uint16 - wantOk bool - }{ - {"", 0, false}, - {"a", 0, false}, - {"ab", 0, false}, - {"abc", 0, false}, - {"abcd", 0xabcd, true}, - {"abcde", 0, false}, - {"9eA1", 0x9ea1, true}, - {"gggg", 0, false}, - {"0000", 0x0000, true}, - {"1234", 0x1234, true}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - got, gotOk := parseHexUint16([]byte(tt.in)) - if got != tt.want || gotOk != tt.wantOk { - t.Errorf("parseHexUint16(%q) = (0x%04x, %v), want (0x%04x, %v)", tt.in, got, gotOk, tt.want, tt.wantOk) - } - }) - } -} - -func TestParseUint(t *testing.T) { - tests := []struct { - in string - want uint64 - wantOk bool - }{ - {"", 0, false}, - {"0", 0, true}, - {"1", 1, true}, - {"-1", 0, false}, - {"1f", 0, false}, - {"00", 0, false}, - {"01", 0, false}, - {"10", 10, true}, - {"10.9", 0, false}, - {" 10", 0, false}, - {"10 ", 0, false}, - {"123456789", 123456789, true}, - {"123456789d", 0, false}, - {"18446744073709551614", math.MaxUint64 - 1, true}, - {"18446744073709551615", math.MaxUint64, true}, - {"18446744073709551616", math.MaxUint64, false}, - {"18446744073709551620", math.MaxUint64, false}, - {"18446744073709551700", math.MaxUint64, false}, - {"18446744073709552000", math.MaxUint64, false}, - {"18446744073709560000", math.MaxUint64, false}, - {"18446744073709600000", math.MaxUint64, false}, - {"18446744073710000000", math.MaxUint64, false}, - {"18446744073800000000", math.MaxUint64, false}, - {"18446744074000000000", math.MaxUint64, false}, - {"18446744080000000000", math.MaxUint64, false}, - {"18446744100000000000", math.MaxUint64, false}, - {"18446745000000000000", math.MaxUint64, false}, - {"18446750000000000000", math.MaxUint64, false}, - {"18446800000000000000", math.MaxUint64, false}, - {"18447000000000000000", math.MaxUint64, false}, - {"18450000000000000000", math.MaxUint64, false}, - {"18500000000000000000", math.MaxUint64, false}, - {"19000000000000000000", math.MaxUint64, false}, - {"19999999999999999999", math.MaxUint64, false}, - {"20000000000000000000", math.MaxUint64, false}, - {"100000000000000000000", math.MaxUint64, false}, - {"99999999999999999999999999999999", math.MaxUint64, false}, - {"99999999999999999999999999999999f", 0, false}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - got, gotOk := ParseUint([]byte(tt.in)) - if got != tt.want || gotOk != tt.wantOk { - t.Errorf("ParseUint(%q) = (%v, %v), want (%v, %v)", tt.in, got, gotOk, tt.want, tt.wantOk) - } - }) - } -} - -func TestParseFloat(t *testing.T) { - tests := []struct { - in string - want32 float64 - want64 float64 - wantOk bool - }{ - {"0", 0, 0, true}, - {"-1", -1, -1, true}, - {"1", 1, 1, true}, - - {"-16777215", -16777215, -16777215, true}, // -(1<<24 - 1) - {"16777215", 16777215, 16777215, true}, // +(1<<24 - 1) - {"-16777216", -16777216, -16777216, true}, // -(1<<24) - {"16777216", 16777216, 16777216, true}, // +(1<<24) - {"-16777217", -16777216, -16777217, true}, // -(1<<24 + 1) - {"16777217", 16777216, 16777217, true}, // +(1<<24 + 1) - - {"-9007199254740991", -9007199254740992, -9007199254740991, true}, // -(1<<53 - 1) - {"9007199254740991", 9007199254740992, 9007199254740991, true}, // +(1<<53 - 1) - {"-9007199254740992", -9007199254740992, -9007199254740992, true}, // -(1<<53) - {"9007199254740992", 9007199254740992, 9007199254740992, true}, // +(1<<53) - {"-9007199254740993", -9007199254740992, -9007199254740992, true}, // -(1<<53 + 1) - {"9007199254740993", 9007199254740992, 9007199254740992, true}, // +(1<<53 + 1) - - {"-1e1000", -math.MaxFloat32, -math.MaxFloat64, false}, - {"1e1000", +math.MaxFloat32, +math.MaxFloat64, false}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - got32, gotOk32 := ParseFloat([]byte(tt.in), 32) - if got32 != tt.want32 || gotOk32 != tt.wantOk { - t.Errorf("ParseFloat(%q, 32) = (%v, %v), want (%v, %v)", tt.in, got32, gotOk32, tt.want32, tt.wantOk) - } - - got64, gotOk64 := ParseFloat([]byte(tt.in), 64) - if got64 != tt.want64 || gotOk64 != tt.wantOk { - t.Errorf("ParseFloat(%q, 64) = (%v, %v), want (%v, %v)", tt.in, got64, gotOk64, tt.want64, tt.wantOk) - } - }) - } -} diff --git a/pkg/encoders/json/internal/jsonwire/encode_test.go b/pkg/encoders/json/internal/jsonwire/encode_test.go deleted file mode 100644 index 6459d20..0000000 --- a/pkg/encoders/json/internal/jsonwire/encode_test.go +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2023 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. - -//go:build goexperiment.jsonv2 - -package jsonwire - -import ( - "bufio" - "bytes" - "compress/gzip" - "crypto/sha256" - "encoding/binary" - "encoding/hex" - "flag" - "math" - "net/http" - "reflect" - "strconv" - "strings" - "testing" - "time" - - "encoding/json/internal/jsonflags" -) - -func TestAppendQuote(t *testing.T) { - tests := []struct { - in string - flags jsonflags.Bools - want string - wantErr error - wantErrUTF8 error - }{ - {"", 0, `""`, nil, nil}, - {"hello", 0, `"hello"`, nil, nil}, - {"\x00", 0, `"\u0000"`, nil, nil}, - {"\x1f", 0, `"\u001f"`, nil, nil}, - {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0, `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, nil, nil}, - {" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", 0, "\" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f\"", nil, nil}, - {" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", jsonflags.EscapeForHTML, "\" !#$%\\u0026'()*+,-./0123456789:;\\u003c=\\u003e?@[]^_`{|}~\x7f\"", nil, nil}, - {" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", jsonflags.EscapeForJS, "\" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f\"", nil, nil}, - {"\u2027\u2028\u2029\u2030", 0, "\"\u2027\u2028\u2029\u2030\"", nil, nil}, - {"\u2027\u2028\u2029\u2030", jsonflags.EscapeForHTML, "\"\u2027\u2028\u2029\u2030\"", nil, nil}, - {"\u2027\u2028\u2029\u2030", jsonflags.EscapeForJS, "\"\u2027\\u2028\\u2029\u2030\"", nil, nil}, - {"x\x80\ufffd", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xff\ufffd", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xc0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xc0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xe0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xe0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xe0\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xf0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xf0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xf0\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xf0\x80\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"x\xed\xba\xad", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, - {"\"\\/\b\f\n\r\t", 0, `"\"\\/\b\f\n\r\t"`, nil, nil}, - {"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).", 0, `"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)."`, nil, nil}, - {"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", 0, "\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", nil, nil}, - {"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", 0, "\"\\u0000\\u001f\u0020\\\"\u0026\u003c\u003e\\\\\u007f\u0080\u2028\u2029\ufffd\U0001f602\"", nil, nil}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - var flags jsonflags.Flags - flags.Set(tt.flags | 1) - - flags.Set(jsonflags.AllowInvalidUTF8 | 1) - got, gotErr := AppendQuote(nil, tt.in, &flags) - if string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { - t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) - } - flags.Set(jsonflags.AllowInvalidUTF8 | 0) - switch got, gotErr := AppendQuote(nil, tt.in, &flags); { - case tt.wantErrUTF8 == nil && (string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr)): - t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) - case tt.wantErrUTF8 != nil && (!strings.HasPrefix(tt.want, string(got)) || !reflect.DeepEqual(gotErr, tt.wantErrUTF8)): - t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErrUTF8) - } - }) - } -} - -func TestAppendNumber(t *testing.T) { - tests := []struct { - in float64 - want32 string - want64 string - }{ - {math.E, "2.7182817", "2.718281828459045"}, - {math.Pi, "3.1415927", "3.141592653589793"}, - {math.SmallestNonzeroFloat32, "1e-45", "1.401298464324817e-45"}, - {math.SmallestNonzeroFloat64, "0", "5e-324"}, - {math.MaxFloat32, "3.4028235e+38", "3.4028234663852886e+38"}, - {math.MaxFloat64, "", "1.7976931348623157e+308"}, - {0.1111111111111111, "0.11111111", "0.1111111111111111"}, - {0.2222222222222222, "0.22222222", "0.2222222222222222"}, - {0.3333333333333333, "0.33333334", "0.3333333333333333"}, - {0.4444444444444444, "0.44444445", "0.4444444444444444"}, - {0.5555555555555555, "0.5555556", "0.5555555555555555"}, - {0.6666666666666666, "0.6666667", "0.6666666666666666"}, - {0.7777777777777777, "0.7777778", "0.7777777777777777"}, - {0.8888888888888888, "0.8888889", "0.8888888888888888"}, - {0.9999999999999999, "1", "0.9999999999999999"}, - - // The following entries are from RFC 8785, appendix B - // which are designed to ensure repeatable formatting of 64-bit floats. - {math.Float64frombits(0x0000000000000000), "0", "0"}, - {math.Float64frombits(0x8000000000000000), "-0", "-0"}, // differs from RFC 8785 - {math.Float64frombits(0x0000000000000001), "0", "5e-324"}, - {math.Float64frombits(0x8000000000000001), "-0", "-5e-324"}, - {math.Float64frombits(0x7fefffffffffffff), "", "1.7976931348623157e+308"}, - {math.Float64frombits(0xffefffffffffffff), "", "-1.7976931348623157e+308"}, - {math.Float64frombits(0x4340000000000000), "9007199000000000", "9007199254740992"}, - {math.Float64frombits(0xc340000000000000), "-9007199000000000", "-9007199254740992"}, - {math.Float64frombits(0x4430000000000000), "295147900000000000000", "295147905179352830000"}, - {math.Float64frombits(0x44b52d02c7e14af5), "1e+23", "9.999999999999997e+22"}, - {math.Float64frombits(0x44b52d02c7e14af6), "1e+23", "1e+23"}, - {math.Float64frombits(0x44b52d02c7e14af7), "1e+23", "1.0000000000000001e+23"}, - {math.Float64frombits(0x444b1ae4d6e2ef4e), "1e+21", "999999999999999700000"}, - {math.Float64frombits(0x444b1ae4d6e2ef4f), "1e+21", "999999999999999900000"}, - {math.Float64frombits(0x444b1ae4d6e2ef50), "1e+21", "1e+21"}, - {math.Float64frombits(0x3eb0c6f7a0b5ed8c), "0.000001", "9.999999999999997e-7"}, - {math.Float64frombits(0x3eb0c6f7a0b5ed8d), "0.000001", "0.000001"}, - {math.Float64frombits(0x41b3de4355555553), "333333340", "333333333.3333332"}, - {math.Float64frombits(0x41b3de4355555554), "333333340", "333333333.33333325"}, - {math.Float64frombits(0x41b3de4355555555), "333333340", "333333333.3333333"}, - {math.Float64frombits(0x41b3de4355555556), "333333340", "333333333.3333334"}, - {math.Float64frombits(0x41b3de4355555557), "333333340", "333333333.33333343"}, - {math.Float64frombits(0xbecbf647612f3696), "-0.0000033333333", "-0.0000033333333333333333"}, - {math.Float64frombits(0x43143ff3c1cb0959), "1424953900000000", "1424953923781206.2"}, - - // The following are select entries from RFC 8785, appendix B, - // but modified for equivalent 32-bit behavior. - {float64(math.Float32frombits(0x65a96815)), "9.999999e+22", "9.999998877476383e+22"}, - {float64(math.Float32frombits(0x65a96816)), "1e+23", "9.999999778196308e+22"}, - {float64(math.Float32frombits(0x65a96817)), "1.0000001e+23", "1.0000000678916234e+23"}, - {float64(math.Float32frombits(0x6258d725)), "999999900000000000000", "999999879303389000000"}, - {float64(math.Float32frombits(0x6258d726)), "999999950000000000000", "999999949672133200000"}, - {float64(math.Float32frombits(0x6258d727)), "1e+21", "1.0000000200408773e+21"}, - {float64(math.Float32frombits(0x6258d728)), "1.0000001e+21", "1.0000000904096215e+21"}, - {float64(math.Float32frombits(0x358637bc)), "9.999999e-7", "9.99999883788405e-7"}, - {float64(math.Float32frombits(0x358637bd)), "0.000001", "9.999999974752427e-7"}, - {float64(math.Float32frombits(0x358637be)), "0.0000010000001", "0.0000010000001111620804"}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - if got32 := string(AppendFloat(nil, tt.in, 32)); got32 != tt.want32 && tt.want32 != "" { - t.Errorf("AppendFloat(nil, %v, 32) = %v, want %v", tt.in, got32, tt.want32) - } - if got64 := string(AppendFloat(nil, tt.in, 64)); got64 != tt.want64 && tt.want64 != "" { - t.Errorf("AppendFloat(nil, %v, 64) = %v, want %v", tt.in, got64, tt.want64) - } - }) - } -} - -// The default of 1e4 lines was chosen since it is sufficiently large to include -// test numbers from all three categories (i.e., static, series, and random). -// Yet, it is sufficiently low to execute quickly relative to other tests. -// -// Processing 1e8 lines takes a minute and processes about 4GiB worth of text. -var testCanonicalNumberLines = flag.Float64("canonical-number-lines", 1e4, "specify the number of lines to check from the canonical numbers testdata") - -// TestCanonicalNumber verifies that appendNumber complies with RFC 8785 -// according to the testdata provided by the reference implementation. -// See https://github.com/cyberphone/json-canonicalization/tree/master/testdata#es6-numbers. -func TestCanonicalNumber(t *testing.T) { - const testfileURL = "https://github.com/cyberphone/json-canonicalization/releases/download/es6testfile/es6testfile100m.txt.gz" - hashes := map[float64]string{ - 1e3: "be18b62b6f69cdab33a7e0dae0d9cfa869fda80ddc712221570f9f40a5878687", - 1e4: "b9f7a8e75ef22a835685a52ccba7f7d6bdc99e34b010992cbc5864cd12be6892", - 1e5: "22776e6d4b49fa294a0d0f349268e5c28808fe7e0cb2bcbe28f63894e494d4c7", - 1e6: "49415fee2c56c77864931bd3624faad425c3c577d6d74e89a83bc725506dad16", - 1e7: "b9f8a44a91d46813b21b9602e72f112613c91408db0b8341fb94603d9db135e0", - 1e8: "0f7dda6b0837dde083c5d6b896f7d62340c8a2415b0c7121d83145e08a755272", - } - wantHash := hashes[*testCanonicalNumberLines] - if wantHash == "" { - t.Fatalf("canonical-number-lines must be one of the following values: 1e3, 1e4, 1e5, 1e6, 1e7, 1e8") - } - numLines := int(*testCanonicalNumberLines) - - // generator returns a function that generates the next float64 to format. - // This implements the algorithm specified in the reference implementation. - generator := func() func() float64 { - static := [...]uint64{ - 0x0000000000000000, 0x8000000000000000, 0x0000000000000001, 0x8000000000000001, - 0xc46696695dbd1cc3, 0xc43211ede4974a35, 0xc3fce97ca0f21056, 0xc3c7213080c1a6ac, - 0xc39280f39a348556, 0xc35d9b1f5d20d557, 0xc327af4c4a80aaac, 0xc2f2f2a36ecd5556, - 0xc2be51057e155558, 0xc28840d131aaaaac, 0xc253670dc1555557, 0xc21f0b4935555557, - 0xc1e8d5d42aaaaaac, 0xc1b3de4355555556, 0xc17fca0555555556, 0xc1496e6aaaaaaaab, - 0xc114585555555555, 0xc0e046aaaaaaaaab, 0xc0aa0aaaaaaaaaaa, 0xc074d55555555555, - 0xc040aaaaaaaaaaab, 0xc00aaaaaaaaaaaab, 0xbfd5555555555555, 0xbfa1111111111111, - 0xbf6b4e81b4e81b4f, 0xbf35d867c3ece2a5, 0xbf0179ec9cbd821e, 0xbecbf647612f3696, - 0xbe965e9f80f29212, 0xbe61e54c672874db, 0xbe2ca213d840baf8, 0xbdf6e80fe033c8c6, - 0xbdc2533fe68fd3d2, 0xbd8d51ffd74c861c, 0xbd5774ccac3d3817, 0xbd22c3d6f030f9ac, - 0xbcee0624b3818f79, 0xbcb804ea293472c7, 0xbc833721ba905bd3, 0xbc4ebe9c5db3c61e, - 0xbc18987d17c304e5, 0xbbe3ad30dfcf371d, 0xbbaf7b816618582f, 0xbb792f9ab81379bf, - 0xbb442615600f9499, 0xbb101e77800c76e1, 0xbad9ca58cce0be35, 0xbaa4a1e0a3e6fe90, - 0xba708180831f320d, 0xba3a68cd9e985016, 0x446696695dbd1cc3, 0x443211ede4974a35, - 0x43fce97ca0f21056, 0x43c7213080c1a6ac, 0x439280f39a348556, 0x435d9b1f5d20d557, - 0x4327af4c4a80aaac, 0x42f2f2a36ecd5556, 0x42be51057e155558, 0x428840d131aaaaac, - 0x4253670dc1555557, 0x421f0b4935555557, 0x41e8d5d42aaaaaac, 0x41b3de4355555556, - 0x417fca0555555556, 0x41496e6aaaaaaaab, 0x4114585555555555, 0x40e046aaaaaaaaab, - 0x40aa0aaaaaaaaaaa, 0x4074d55555555555, 0x4040aaaaaaaaaaab, 0x400aaaaaaaaaaaab, - 0x3fd5555555555555, 0x3fa1111111111111, 0x3f6b4e81b4e81b4f, 0x3f35d867c3ece2a5, - 0x3f0179ec9cbd821e, 0x3ecbf647612f3696, 0x3e965e9f80f29212, 0x3e61e54c672874db, - 0x3e2ca213d840baf8, 0x3df6e80fe033c8c6, 0x3dc2533fe68fd3d2, 0x3d8d51ffd74c861c, - 0x3d5774ccac3d3817, 0x3d22c3d6f030f9ac, 0x3cee0624b3818f79, 0x3cb804ea293472c7, - 0x3c833721ba905bd3, 0x3c4ebe9c5db3c61e, 0x3c18987d17c304e5, 0x3be3ad30dfcf371d, - 0x3baf7b816618582f, 0x3b792f9ab81379bf, 0x3b442615600f9499, 0x3b101e77800c76e1, - 0x3ad9ca58cce0be35, 0x3aa4a1e0a3e6fe90, 0x3a708180831f320d, 0x3a3a68cd9e985016, - 0x4024000000000000, 0x4014000000000000, 0x3fe0000000000000, 0x3fa999999999999a, - 0x3f747ae147ae147b, 0x3f40624dd2f1a9fc, 0x3f0a36e2eb1c432d, 0x3ed4f8b588e368f1, - 0x3ea0c6f7a0b5ed8d, 0x3e6ad7f29abcaf48, 0x3e35798ee2308c3a, 0x3ed539223589fa95, - 0x3ed4ff26cd5a7781, 0x3ed4f95a762283ff, 0x3ed4f8c60703520c, 0x3ed4f8b72f19cd0d, - 0x3ed4f8b5b31c0c8d, 0x3ed4f8b58d1c461a, 0x3ed4f8b5894f7f0e, 0x3ed4f8b588ee37f3, - 0x3ed4f8b588e47da4, 0x3ed4f8b588e3849c, 0x3ed4f8b588e36bb5, 0x3ed4f8b588e36937, - 0x3ed4f8b588e368f8, 0x3ed4f8b588e368f1, 0x3ff0000000000000, 0xbff0000000000000, - 0xbfeffffffffffffa, 0xbfeffffffffffffb, 0x3feffffffffffffa, 0x3feffffffffffffb, - 0x3feffffffffffffc, 0x3feffffffffffffe, 0xbfefffffffffffff, 0xbfefffffffffffff, - 0x3fefffffffffffff, 0x3fefffffffffffff, 0x3fd3333333333332, 0x3fd3333333333333, - 0x3fd3333333333334, 0x0010000000000000, 0x000ffffffffffffd, 0x000fffffffffffff, - 0x7fefffffffffffff, 0xffefffffffffffff, 0x4340000000000000, 0xc340000000000000, - 0x4430000000000000, 0x44b52d02c7e14af5, 0x44b52d02c7e14af6, 0x44b52d02c7e14af7, - 0x444b1ae4d6e2ef4e, 0x444b1ae4d6e2ef4f, 0x444b1ae4d6e2ef50, 0x3eb0c6f7a0b5ed8c, - 0x3eb0c6f7a0b5ed8d, 0x41b3de4355555553, 0x41b3de4355555554, 0x41b3de4355555555, - 0x41b3de4355555556, 0x41b3de4355555557, 0xbecbf647612f3696, 0x43143ff3c1cb0959, - } - var state struct { - idx int - data []byte - block [sha256.Size]byte - } - return func() float64 { - const numSerial = 2000 - var f float64 - switch { - case state.idx < len(static): - f = math.Float64frombits(static[state.idx]) - case state.idx < len(static)+numSerial: - f = math.Float64frombits(0x0010000000000000 + uint64(state.idx-len(static))) - default: - for f == 0 || math.IsNaN(f) || math.IsInf(f, 0) { - if len(state.data) == 0 { - state.block = sha256.Sum256(state.block[:]) - state.data = state.block[:] - } - f = math.Float64frombits(binary.LittleEndian.Uint64(state.data)) - state.data = state.data[8:] - } - } - state.idx++ - return f - } - } - - // Pass through the test twice. In the first pass we only hash the output, - // while in the second pass we check every line against the golden testdata. - // If the hashes match in the first pass, then we skip the second pass. - for _, checkGolden := range []bool{false, true} { - var br *bufio.Reader // for line-by-line reading of es6testfile100m.txt - if checkGolden { - resp, err := http.Get(testfileURL) - if err != nil { - t.Fatalf("http.Get error: %v", err) - } - defer resp.Body.Close() - - zr, err := gzip.NewReader(resp.Body) - if err != nil { - t.Fatalf("gzip.NewReader error: %v", err) - } - - br = bufio.NewReader(zr) - } - - // appendNumberJCS differs from appendNumber only for -0. - appendNumberJCS := func(b []byte, f float64) []byte { - if math.Signbit(f) && f == 0 { - return append(b, '0') - } - return AppendFloat(b, f, 64) - } - - var gotLine []byte - next := generator() - hash := sha256.New() - start := time.Now() - lastPrint := start - for n := 1; n <= numLines; n++ { - // Generate the formatted line for this number. - f := next() - gotLine = gotLine[:0] // reset from previous usage - gotLine = strconv.AppendUint(gotLine, math.Float64bits(f), 16) - gotLine = append(gotLine, ',') - gotLine = appendNumberJCS(gotLine, f) - gotLine = append(gotLine, '\n') - hash.Write(gotLine) - - // Check that the formatted line matches. - if checkGolden { - wantLine, err := br.ReadBytes('\n') - if err != nil { - t.Fatalf("bufio.Reader.ReadBytes error: %v", err) - } - if !bytes.Equal(gotLine, wantLine) { - t.Errorf("mismatch on line %d:\n\tgot %v\n\twant %v", - n, strings.TrimSpace(string(gotLine)), strings.TrimSpace(string(wantLine))) - } - } - - // Print progress. - if now := time.Now(); now.Sub(lastPrint) > time.Second || n == numLines { - remaining := float64(now.Sub(start)) * float64(numLines-n) / float64(n) - t.Logf("%0.3f%% (%v remaining)", - 100.0*float64(n)/float64(numLines), - time.Duration(remaining).Round(time.Second)) - lastPrint = now - } - } - - gotHash := hex.EncodeToString(hash.Sum(nil)) - if gotHash == wantHash { - return // hashes match, no need to check golden testdata - } - } -} diff --git a/pkg/encoders/json/internal/jsonwire/wire_test.go b/pkg/encoders/json/internal/jsonwire/wire_test.go deleted file mode 100644 index a0bf1d1..0000000 --- a/pkg/encoders/json/internal/jsonwire/wire_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2023 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. - -//go:build goexperiment.jsonv2 - -package jsonwire - -import ( - "cmp" - "slices" - "testing" - "unicode/utf16" - "unicode/utf8" -) - -func TestQuoteRune(t *testing.T) { - tests := []struct{ in, want string }{ - {"x", `'x'`}, - {"\n", `'\n'`}, - {"'", `'\''`}, - {"\xff", `'\xff'`}, - {"💩", `'💩'`}, - {"💩"[:1], `'\xf0'`}, - {"\uffff", `'\uffff'`}, - {"\U00101234", `'\U00101234'`}, - } - for _, tt := range tests { - got := QuoteRune([]byte(tt.in)) - if got != tt.want { - t.Errorf("quoteRune(%q) = %s, want %s", tt.in, got, tt.want) - } - } -} - -var compareUTF16Testdata = []string{"", "\r", "1", "f\xfe", "f\xfe\xff", "f\xff", "\u0080", "\u00f6", "\u20ac", "\U0001f600", "\ufb33"} - -func TestCompareUTF16(t *testing.T) { - for i, si := range compareUTF16Testdata { - for j, sj := range compareUTF16Testdata { - got := CompareUTF16([]byte(si), []byte(sj)) - want := cmp.Compare(i, j) - if got != want { - t.Errorf("CompareUTF16(%q, %q) = %v, want %v", si, sj, got, want) - } - } - } -} - -func FuzzCompareUTF16(f *testing.F) { - for _, td1 := range compareUTF16Testdata { - for _, td2 := range compareUTF16Testdata { - f.Add([]byte(td1), []byte(td2)) - } - } - - // CompareUTF16Simple is identical to CompareUTF16, - // but relies on naively converting a string to a []uint16 codepoints. - // It is easy to verify as correct, but is slow. - CompareUTF16Simple := func(x, y []byte) int { - ux := utf16.Encode([]rune(string(x))) - uy := utf16.Encode([]rune(string(y))) - return slices.Compare(ux, uy) - } - - f.Fuzz(func(t *testing.T, s1, s2 []byte) { - // Compare the optimized and simplified implementations. - got := CompareUTF16(s1, s2) - want := CompareUTF16Simple(s1, s2) - if got != want && utf8.Valid(s1) && utf8.Valid(s2) { - t.Errorf("CompareUTF16(%q, %q) = %v, want %v", s1, s2, got, want) - } - }) -} - -func TestTruncatePointer(t *testing.T) { - tests := []struct{ in, want string }{ - {"hello", "hello"}, - {"/a/b/c", "/a/b/c"}, - {"/a/b/c/d/e/f/g", "/a/b/…/f/g"}, - {"supercalifragilisticexpialidocious", "super…cious"}, - {"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/supe…/…cious"}, - {"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/supe…/…/…cious"}, - {"/a/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/a/…/…cious"}, - {"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious/b", "/supe…/…/b"}, - {"/fizz/buzz/bazz", "/fizz/…/bazz"}, - {"/fizz/buzz/bazz/razz", "/fizz/…/razz"}, - {"/////////////////////////////", "/////…/////"}, - {"/🎄❤️✨/🎁✅😊/🎅🔥⭐", "/🎄…/…/…⭐"}, - } - for _, tt := range tests { - got := TruncatePointer(tt.in, 10) - if got != tt.want { - t.Errorf("TruncatePointer(%q) = %q, want %q", tt.in, got, tt.want) - } - } - -} diff --git a/pkg/encoders/json/jsontext/coder_test.go b/pkg/encoders/json/jsontext/coder_test.go deleted file mode 100644 index 4a9efb3..0000000 --- a/pkg/encoders/json/jsontext/coder_test.go +++ /dev/null @@ -1,856 +0,0 @@ -// Copyright 2020 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. - -//go:build goexperiment.jsonv2 - -package jsontext - -import ( - "bytes" - "errors" - "io" - "math" - "math/rand" - "path" - "reflect" - "strings" - "testing" - - "encoding/json/internal/jsontest" - "encoding/json/internal/jsonwire" -) - -func E(err error) *SyntacticError { - return &SyntacticError{Err: err} -} - -func newInvalidCharacterError(prefix, where string) *SyntacticError { - return E(jsonwire.NewInvalidCharacterError(prefix, where)) -} - -func newInvalidEscapeSequenceError(what string) *SyntacticError { - return E(jsonwire.NewInvalidEscapeSequenceError(what)) -} - -func (e *SyntacticError) withPos(prefix string, pointer Pointer) *SyntacticError { - e.ByteOffset = int64(len(prefix)) - e.JSONPointer = pointer - return e -} - -func equalError(x, y error) bool { - return reflect.DeepEqual(x, y) -} - -var ( - zeroToken Token - zeroValue Value -) - -// tokOrVal is either a Token or a Value. -type tokOrVal interface{ Kind() Kind } - -type coderTestdataEntry struct { - name jsontest.CaseName - in string - outCompacted string - outEscaped string // outCompacted if empty; escapes all runes in a string - outIndented string // outCompacted if empty; uses " " for indent prefix and "\t" for indent - outCanonicalized string // outCompacted if empty - tokens []Token - pointers []Pointer -} - -var coderTestdata = []coderTestdataEntry{{ - name: jsontest.Name("Null"), - in: ` null `, - outCompacted: `null`, - tokens: []Token{Null}, - pointers: []Pointer{""}, -}, { - name: jsontest.Name("False"), - in: ` false `, - outCompacted: `false`, - tokens: []Token{False}, -}, { - name: jsontest.Name("True"), - in: ` true `, - outCompacted: `true`, - tokens: []Token{True}, -}, { - name: jsontest.Name("EmptyString"), - in: ` "" `, - outCompacted: `""`, - tokens: []Token{String("")}, -}, { - name: jsontest.Name("SimpleString"), - in: ` "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" `, - outCompacted: `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"`, - outEscaped: `"\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a"`, - tokens: []Token{String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")}, -}, { - name: jsontest.Name("ComplicatedString"), - in: " \"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + ` \ud800\udead \"\\\/\b\f\n\r\t \u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009" `, - outCompacted: "\"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + " 𐊭 \\\"\\\\/\\b\\f\\n\\r\\t \\\"\\\\/\\b\\f\\n\\r\\t\"", - outEscaped: `"\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u4e16\u754c\u0020\ud83c\udf1f\u2605\u2606\u2729\ud83c\udf20\u0020\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02\u0020\ud800\udead\u0020\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009\u0020\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, - outCanonicalized: `"Hello, 世界 🌟★☆✩🌠 €ö€힙דּ�😂 𐊭 \"\\/\b\f\n\r\t \"\\/\b\f\n\r\t"`, - tokens: []Token{rawToken("\"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + " 𐊭 \\\"\\\\/\\b\\f\\n\\r\\t \\\"\\\\/\\b\\f\\n\\r\\t\"")}, -}, { - name: jsontest.Name("ZeroNumber"), - in: ` 0 `, - outCompacted: `0`, - tokens: []Token{Uint(0)}, -}, { - name: jsontest.Name("SimpleNumber"), - in: ` 123456789 `, - outCompacted: `123456789`, - tokens: []Token{Uint(123456789)}, -}, { - name: jsontest.Name("NegativeNumber"), - in: ` -123456789 `, - outCompacted: `-123456789`, - tokens: []Token{Int(-123456789)}, -}, { - name: jsontest.Name("FractionalNumber"), - in: " 0.123456789 ", - outCompacted: `0.123456789`, - tokens: []Token{Float(0.123456789)}, -}, { - name: jsontest.Name("ExponentNumber"), - in: " 0e12456789 ", - outCompacted: `0e12456789`, - outCanonicalized: `0`, - tokens: []Token{rawToken(`0e12456789`)}, -}, { - name: jsontest.Name("ExponentNumberP"), - in: " 0e+12456789 ", - outCompacted: `0e+12456789`, - outCanonicalized: `0`, - tokens: []Token{rawToken(`0e+12456789`)}, -}, { - name: jsontest.Name("ExponentNumberN"), - in: " 0e-12456789 ", - outCompacted: `0e-12456789`, - outCanonicalized: `0`, - tokens: []Token{rawToken(`0e-12456789`)}, -}, { - name: jsontest.Name("ComplicatedNumber"), - in: ` -123456789.987654321E+0123456789 `, - outCompacted: `-123456789.987654321E+0123456789`, - outCanonicalized: `-1.7976931348623157e+308`, - tokens: []Token{rawToken(`-123456789.987654321E+0123456789`)}, -}, { - name: jsontest.Name("Numbers"), - in: ` [ - 0, -0, 0.0, -0.0, 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, 1e1000, - -5e-324, 1e+100, 1.7976931348623157e+308, - 9007199254740990, 9007199254740991, 9007199254740992, 9007199254740993, 9007199254740994, - -9223372036854775808, 9223372036854775807, 0, 18446744073709551615 - ] `, - outCompacted: "[0,-0,0.0,-0.0,1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001,1e1000,-5e-324,1e+100,1.7976931348623157e+308,9007199254740990,9007199254740991,9007199254740992,9007199254740993,9007199254740994,-9223372036854775808,9223372036854775807,0,18446744073709551615]", - outIndented: `[ - 0, - -0, - 0.0, - -0.0, - 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, - 1e1000, - -5e-324, - 1e+100, - 1.7976931348623157e+308, - 9007199254740990, - 9007199254740991, - 9007199254740992, - 9007199254740993, - 9007199254740994, - -9223372036854775808, - 9223372036854775807, - 0, - 18446744073709551615 - ]`, - outCanonicalized: `[0,0,0,0,1,1.7976931348623157e+308,-5e-324,1e+100,1.7976931348623157e+308,9007199254740990,9007199254740991,9007199254740992,9007199254740992,9007199254740994,-9223372036854776000,9223372036854776000,0,18446744073709552000]`, - tokens: []Token{ - BeginArray, - Float(0), Float(math.Copysign(0, -1)), rawToken(`0.0`), rawToken(`-0.0`), rawToken(`1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001`), rawToken(`1e1000`), - Float(-5e-324), Float(1e100), Float(1.7976931348623157e+308), - Float(9007199254740990), Float(9007199254740991), Float(9007199254740992), rawToken(`9007199254740993`), rawToken(`9007199254740994`), - Int(minInt64), Int(maxInt64), Uint(minUint64), Uint(maxUint64), - EndArray, - }, - pointers: []Pointer{ - "", "/0", "/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/10", "/11", "/12", "/13", "/14", "/15", "/16", "/17", "", - }, -}, { - name: jsontest.Name("ObjectN0"), - in: ` { } `, - outCompacted: `{}`, - tokens: []Token{BeginObject, EndObject}, - pointers: []Pointer{"", ""}, -}, { - name: jsontest.Name("ObjectN1"), - in: ` { "0" : 0 } `, - outCompacted: `{"0":0}`, - outEscaped: `{"\u0030":0}`, - outIndented: `{ - "0": 0 - }`, - tokens: []Token{BeginObject, String("0"), Uint(0), EndObject}, - pointers: []Pointer{"", "/0", "/0", ""}, -}, { - name: jsontest.Name("ObjectN2"), - in: ` { "0" : 0 , "1" : 1 } `, - outCompacted: `{"0":0,"1":1}`, - outEscaped: `{"\u0030":0,"\u0031":1}`, - outIndented: `{ - "0": 0, - "1": 1 - }`, - tokens: []Token{BeginObject, String("0"), Uint(0), String("1"), Uint(1), EndObject}, - pointers: []Pointer{"", "/0", "/0", "/1", "/1", ""}, -}, { - name: jsontest.Name("ObjectNested"), - in: ` { "0" : { "1" : { "2" : { "3" : { "4" : { } } } } } } `, - outCompacted: `{"0":{"1":{"2":{"3":{"4":{}}}}}}`, - outEscaped: `{"\u0030":{"\u0031":{"\u0032":{"\u0033":{"\u0034":{}}}}}}`, - outIndented: `{ - "0": { - "1": { - "2": { - "3": { - "4": {} - } - } - } - } - }`, - tokens: []Token{BeginObject, String("0"), BeginObject, String("1"), BeginObject, String("2"), BeginObject, String("3"), BeginObject, String("4"), BeginObject, EndObject, EndObject, EndObject, EndObject, EndObject, EndObject}, - pointers: []Pointer{ - "", - "/0", "/0", - "/0/1", "/0/1", - "/0/1/2", "/0/1/2", - "/0/1/2/3", "/0/1/2/3", - "/0/1/2/3/4", "/0/1/2/3/4", - "/0/1/2/3/4", - "/0/1/2/3", - "/0/1/2", - "/0/1", - "/0", - "", - }, -}, { - name: jsontest.Name("ObjectSuperNested"), - in: `{"": { - "44444": { - "6666666": "ccccccc", - "77777777": "bb", - "555555": "aaaa" - }, - "0": { - "3333": "bbb", - "11": "", - "222": "aaaaa" - } - }}`, - outCompacted: `{"":{"44444":{"6666666":"ccccccc","77777777":"bb","555555":"aaaa"},"0":{"3333":"bbb","11":"","222":"aaaaa"}}}`, - outEscaped: `{"":{"\u0034\u0034\u0034\u0034\u0034":{"\u0036\u0036\u0036\u0036\u0036\u0036\u0036":"\u0063\u0063\u0063\u0063\u0063\u0063\u0063","\u0037\u0037\u0037\u0037\u0037\u0037\u0037\u0037":"\u0062\u0062","\u0035\u0035\u0035\u0035\u0035\u0035":"\u0061\u0061\u0061\u0061"},"\u0030":{"\u0033\u0033\u0033\u0033":"\u0062\u0062\u0062","\u0031\u0031":"","\u0032\u0032\u0032":"\u0061\u0061\u0061\u0061\u0061"}}}`, - outIndented: `{ - "": { - "44444": { - "6666666": "ccccccc", - "77777777": "bb", - "555555": "aaaa" - }, - "0": { - "3333": "bbb", - "11": "", - "222": "aaaaa" - } - } - }`, - outCanonicalized: `{"":{"0":{"11":"","222":"aaaaa","3333":"bbb"},"44444":{"555555":"aaaa","6666666":"ccccccc","77777777":"bb"}}}`, - tokens: []Token{ - BeginObject, - String(""), - BeginObject, - String("44444"), - BeginObject, - String("6666666"), String("ccccccc"), - String("77777777"), String("bb"), - String("555555"), String("aaaa"), - EndObject, - String("0"), - BeginObject, - String("3333"), String("bbb"), - String("11"), String(""), - String("222"), String("aaaaa"), - EndObject, - EndObject, - EndObject, - }, - pointers: []Pointer{ - "", - "/", "/", - "//44444", "//44444", - "//44444/6666666", "//44444/6666666", - "//44444/77777777", "//44444/77777777", - "//44444/555555", "//44444/555555", - "//44444", - "//0", "//0", - "//0/3333", "//0/3333", - "//0/11", "//0/11", - "//0/222", "//0/222", - "//0", - "/", - "", - }, -}, { - name: jsontest.Name("ArrayN0"), - in: ` [ ] `, - outCompacted: `[]`, - tokens: []Token{BeginArray, EndArray}, - pointers: []Pointer{"", ""}, -}, { - name: jsontest.Name("ArrayN1"), - in: ` [ 0 ] `, - outCompacted: `[0]`, - outIndented: `[ - 0 - ]`, - tokens: []Token{BeginArray, Uint(0), EndArray}, - pointers: []Pointer{"", "/0", ""}, -}, { - name: jsontest.Name("ArrayN2"), - in: ` [ 0 , 1 ] `, - outCompacted: `[0,1]`, - outIndented: `[ - 0, - 1 - ]`, - tokens: []Token{BeginArray, Uint(0), Uint(1), EndArray}, -}, { - name: jsontest.Name("ArrayNested"), - in: ` [ [ [ [ [ ] ] ] ] ] `, - outCompacted: `[[[[[]]]]]`, - outIndented: `[ - [ - [ - [ - [] - ] - ] - ] - ]`, - tokens: []Token{BeginArray, BeginArray, BeginArray, BeginArray, BeginArray, EndArray, EndArray, EndArray, EndArray, EndArray}, - pointers: []Pointer{ - "", - "/0", - "/0/0", - "/0/0/0", - "/0/0/0/0", - "/0/0/0/0", - "/0/0/0", - "/0/0", - "/0", - "", - }, -}, { - name: jsontest.Name("Everything"), - in: ` { - "literals" : [ null , false , true ], - "string" : "Hello, 世界" , - "number" : 3.14159 , - "arrayN0" : [ ] , - "arrayN1" : [ 0 ] , - "arrayN2" : [ 0 , 1 ] , - "objectN0" : { } , - "objectN1" : { "0" : 0 } , - "objectN2" : { "0" : 0 , "1" : 1 } - } `, - outCompacted: `{"literals":[null,false,true],"string":"Hello, 世界","number":3.14159,"arrayN0":[],"arrayN1":[0],"arrayN2":[0,1],"objectN0":{},"objectN1":{"0":0},"objectN2":{"0":0,"1":1}}`, - outEscaped: `{"\u006c\u0069\u0074\u0065\u0072\u0061\u006c\u0073":[null,false,true],"\u0073\u0074\u0072\u0069\u006e\u0067":"\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u4e16\u754c","\u006e\u0075\u006d\u0062\u0065\u0072":3.14159,"\u0061\u0072\u0072\u0061\u0079\u004e\u0030":[],"\u0061\u0072\u0072\u0061\u0079\u004e\u0031":[0],"\u0061\u0072\u0072\u0061\u0079\u004e\u0032":[0,1],"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0030":{},"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0031":{"\u0030":0},"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0032":{"\u0030":0,"\u0031":1}}`, - outIndented: `{ - "literals": [ - null, - false, - true - ], - "string": "Hello, 世界", - "number": 3.14159, - "arrayN0": [], - "arrayN1": [ - 0 - ], - "arrayN2": [ - 0, - 1 - ], - "objectN0": {}, - "objectN1": { - "0": 0 - }, - "objectN2": { - "0": 0, - "1": 1 - } - }`, - outCanonicalized: `{"arrayN0":[],"arrayN1":[0],"arrayN2":[0,1],"literals":[null,false,true],"number":3.14159,"objectN0":{},"objectN1":{"0":0},"objectN2":{"0":0,"1":1},"string":"Hello, 世界"}`, - tokens: []Token{ - BeginObject, - String("literals"), BeginArray, Null, False, True, EndArray, - String("string"), String("Hello, 世界"), - String("number"), Float(3.14159), - String("arrayN0"), BeginArray, EndArray, - String("arrayN1"), BeginArray, Uint(0), EndArray, - String("arrayN2"), BeginArray, Uint(0), Uint(1), EndArray, - String("objectN0"), BeginObject, EndObject, - String("objectN1"), BeginObject, String("0"), Uint(0), EndObject, - String("objectN2"), BeginObject, String("0"), Uint(0), String("1"), Uint(1), EndObject, - EndObject, - }, - pointers: []Pointer{ - "", - "/literals", "/literals", - "/literals/0", - "/literals/1", - "/literals/2", - "/literals", - "/string", "/string", - "/number", "/number", - "/arrayN0", "/arrayN0", "/arrayN0", - "/arrayN1", "/arrayN1", - "/arrayN1/0", - "/arrayN1", - "/arrayN2", "/arrayN2", - "/arrayN2/0", - "/arrayN2/1", - "/arrayN2", - "/objectN0", "/objectN0", "/objectN0", - "/objectN1", "/objectN1", - "/objectN1/0", "/objectN1/0", - "/objectN1", - "/objectN2", "/objectN2", - "/objectN2/0", "/objectN2/0", - "/objectN2/1", "/objectN2/1", - "/objectN2", - "", - }, -}} - -// TestCoderInterleaved tests that we can interleave calls that operate on -// tokens and raw values. The only error condition is trying to operate on a -// raw value when the next token is an end of object or array. -func TestCoderInterleaved(t *testing.T) { - for _, td := range coderTestdata { - // In TokenFirst and ValueFirst, alternate between tokens and values. - // In TokenDelims, only use tokens for object and array delimiters. - for _, modeName := range []string{"TokenFirst", "ValueFirst", "TokenDelims"} { - t.Run(path.Join(td.name.Name, modeName), func(t *testing.T) { - testCoderInterleaved(t, td.name.Where, modeName, td) - }) - } - } -} -func testCoderInterleaved(t *testing.T, where jsontest.CasePos, modeName string, td coderTestdataEntry) { - src := strings.NewReader(td.in) - dst := new(bytes.Buffer) - dec := NewDecoder(src) - enc := NewEncoder(dst) - tickTock := modeName == "TokenFirst" - for { - if modeName == "TokenDelims" { - switch dec.PeekKind() { - case '{', '}', '[', ']': - tickTock = true // as token - default: - tickTock = false // as value - } - } - if tickTock { - tok, err := dec.ReadToken() - if err != nil { - if err == io.EOF { - break - } - t.Fatalf("%s: Decoder.ReadToken error: %v", where, err) - } - if err := enc.WriteToken(tok); err != nil { - t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) - } - } else { - val, err := dec.ReadValue() - if err != nil { - // It is a syntactic error to call ReadValue - // at the end of an object or array. - // Retry as a ReadToken call. - expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']' - if expectError { - if !errors.As(err, new(*SyntacticError)) { - t.Fatalf("%s: Decoder.ReadToken error is %T, want %T", where, err, new(SyntacticError)) - } - tickTock = !tickTock - continue - } - - if err == io.EOF { - break - } - t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) - } - if err := enc.WriteValue(val); err != nil { - t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) - } - } - tickTock = !tickTock - } - - got := dst.String() - want := td.outCompacted + "\n" - if got != want { - t.Fatalf("%s: output mismatch:\ngot %q\nwant %q", where, got, want) - } -} - -func TestCoderStackPointer(t *testing.T) { - tests := []struct { - token Token - want Pointer - }{ - {Null, ""}, - - {BeginArray, ""}, - {EndArray, ""}, - - {BeginArray, ""}, - {Bool(true), "/0"}, - {EndArray, ""}, - - {BeginArray, ""}, - {String("hello"), "/0"}, - {String("goodbye"), "/1"}, - {EndArray, ""}, - - {BeginObject, ""}, - {EndObject, ""}, - - {BeginObject, ""}, - {String("hello"), "/hello"}, - {String("goodbye"), "/hello"}, - {EndObject, ""}, - - {BeginObject, ""}, - {String(""), "/"}, - {Null, "/"}, - {String("0"), "/0"}, - {Null, "/0"}, - {String("~"), "/~0"}, - {Null, "/~0"}, - {String("/"), "/~1"}, - {Null, "/~1"}, - {String("a//b~/c/~d~~e"), "/a~1~1b~0~1c~1~0d~0~0e"}, - {Null, "/a~1~1b~0~1c~1~0d~0~0e"}, - {String(" \r\n\t"), "/ \r\n\t"}, - {Null, "/ \r\n\t"}, - {EndObject, ""}, - - {BeginArray, ""}, - {BeginObject, "/0"}, - {String(""), "/0/"}, - {BeginArray, "/0/"}, - {BeginObject, "/0//0"}, - {String("#"), "/0//0/#"}, - {Null, "/0//0/#"}, - {EndObject, "/0//0"}, - {EndArray, "/0/"}, - {EndObject, "/0"}, - {EndArray, ""}, - } - - for _, allowDupes := range []bool{false, true} { - var name string - switch allowDupes { - case false: - name = "RejectDuplicateNames" - case true: - name = "AllowDuplicateNames" - } - - t.Run(name, func(t *testing.T) { - bb := new(bytes.Buffer) - - enc := NewEncoder(bb, AllowDuplicateNames(allowDupes)) - for i, tt := range tests { - if err := enc.WriteToken(tt.token); err != nil { - t.Fatalf("%d: Encoder.WriteToken error: %v", i, err) - } - if got := enc.StackPointer(); got != tests[i].want { - t.Fatalf("%d: Encoder.StackPointer = %v, want %v", i, got, tests[i].want) - } - } - - dec := NewDecoder(bb, AllowDuplicateNames(allowDupes)) - for i := range tests { - if _, err := dec.ReadToken(); err != nil { - t.Fatalf("%d: Decoder.ReadToken error: %v", i, err) - } - if got := dec.StackPointer(); got != tests[i].want { - t.Fatalf("%d: Decoder.StackPointer = %v, want %v", i, got, tests[i].want) - } - } - }) - } -} - -func TestCoderMaxDepth(t *testing.T) { - trimArray := func(b []byte) []byte { return b[len(`[`) : len(b)-len(`]`)] } - maxArrays := []byte(strings.Repeat(`[`, maxNestingDepth+1) + strings.Repeat(`]`, maxNestingDepth+1)) - trimObject := func(b []byte) []byte { return b[len(`{"":`) : len(b)-len(`}`)] } - maxObjects := []byte(strings.Repeat(`{"":`, maxNestingDepth+1) + `""` + strings.Repeat(`}`, maxNestingDepth+1)) - - t.Run("Decoder", func(t *testing.T) { - var dec Decoder - checkReadToken := func(t *testing.T, wantKind Kind, wantErr error) { - t.Helper() - if tok, err := dec.ReadToken(); tok.Kind() != wantKind || !equalError(err, wantErr) { - t.Fatalf("Decoder.ReadToken = (%q, %v), want (%q, %v)", byte(tok.Kind()), err, byte(wantKind), wantErr) - } - } - checkReadValue := func(t *testing.T, wantLen int, wantErr error) { - t.Helper() - if val, err := dec.ReadValue(); len(val) != wantLen || !equalError(err, wantErr) { - t.Fatalf("Decoder.ReadValue = (%d, %v), want (%d, %v)", len(val), err, wantLen, wantErr) - } - } - - t.Run("ArraysValid/SingleValue", func(t *testing.T) { - dec.s.reset(trimArray(maxArrays), nil) - checkReadValue(t, maxNestingDepth*len(`[]`), nil) - }) - t.Run("ArraysValid/TokenThenValue", func(t *testing.T) { - dec.s.reset(trimArray(maxArrays), nil) - checkReadToken(t, '[', nil) - checkReadValue(t, (maxNestingDepth-1)*len(`[]`), nil) - checkReadToken(t, ']', nil) - }) - t.Run("ArraysValid/AllTokens", func(t *testing.T) { - dec.s.reset(trimArray(maxArrays), nil) - for range maxNestingDepth { - checkReadToken(t, '[', nil) - } - for range maxNestingDepth { - checkReadToken(t, ']', nil) - } - }) - - wantErr := &SyntacticError{ - ByteOffset: maxNestingDepth, - JSONPointer: Pointer(strings.Repeat("/0", maxNestingDepth)), - Err: errMaxDepth, - } - t.Run("ArraysInvalid/SingleValue", func(t *testing.T) { - dec.s.reset(maxArrays, nil) - checkReadValue(t, 0, wantErr) - }) - t.Run("ArraysInvalid/TokenThenValue", func(t *testing.T) { - dec.s.reset(maxArrays, nil) - checkReadToken(t, '[', nil) - checkReadValue(t, 0, wantErr) - }) - t.Run("ArraysInvalid/AllTokens", func(t *testing.T) { - dec.s.reset(maxArrays, nil) - for range maxNestingDepth { - checkReadToken(t, '[', nil) - } - checkReadValue(t, 0, wantErr) - }) - - t.Run("ObjectsValid/SingleValue", func(t *testing.T) { - dec.s.reset(trimObject(maxObjects), nil) - checkReadValue(t, maxNestingDepth*len(`{"":}`)+len(`""`), nil) - }) - t.Run("ObjectsValid/TokenThenValue", func(t *testing.T) { - dec.s.reset(trimObject(maxObjects), nil) - checkReadToken(t, '{', nil) - checkReadToken(t, '"', nil) - checkReadValue(t, (maxNestingDepth-1)*len(`{"":}`)+len(`""`), nil) - checkReadToken(t, '}', nil) - }) - t.Run("ObjectsValid/AllTokens", func(t *testing.T) { - dec.s.reset(trimObject(maxObjects), nil) - for range maxNestingDepth { - checkReadToken(t, '{', nil) - checkReadToken(t, '"', nil) - } - checkReadToken(t, '"', nil) - for range maxNestingDepth { - checkReadToken(t, '}', nil) - } - }) - - wantErr = &SyntacticError{ - ByteOffset: maxNestingDepth * int64(len(`{"":`)), - JSONPointer: Pointer(strings.Repeat("/", maxNestingDepth)), - Err: errMaxDepth, - } - t.Run("ObjectsInvalid/SingleValue", func(t *testing.T) { - dec.s.reset(maxObjects, nil) - checkReadValue(t, 0, wantErr) - }) - t.Run("ObjectsInvalid/TokenThenValue", func(t *testing.T) { - dec.s.reset(maxObjects, nil) - checkReadToken(t, '{', nil) - checkReadToken(t, '"', nil) - checkReadValue(t, 0, wantErr) - }) - t.Run("ObjectsInvalid/AllTokens", func(t *testing.T) { - dec.s.reset(maxObjects, nil) - for range maxNestingDepth { - checkReadToken(t, '{', nil) - checkReadToken(t, '"', nil) - } - checkReadToken(t, 0, wantErr) - }) - }) - - t.Run("Encoder", func(t *testing.T) { - var enc Encoder - checkWriteToken := func(t *testing.T, tok Token, wantErr error) { - t.Helper() - if err := enc.WriteToken(tok); !equalError(err, wantErr) { - t.Fatalf("Encoder.WriteToken = %v, want %v", err, wantErr) - } - } - checkWriteValue := func(t *testing.T, val Value, wantErr error) { - t.Helper() - if err := enc.WriteValue(val); !equalError(err, wantErr) { - t.Fatalf("Encoder.WriteValue = %v, want %v", err, wantErr) - } - } - - wantErr := &SyntacticError{ - ByteOffset: maxNestingDepth, - JSONPointer: Pointer(strings.Repeat("/0", maxNestingDepth)), - Err: errMaxDepth, - } - t.Run("Arrays/SingleValue", func(t *testing.T) { - enc.s.reset(enc.s.Buf[:0], nil) - checkWriteValue(t, maxArrays, wantErr) - checkWriteValue(t, trimArray(maxArrays), nil) - }) - t.Run("Arrays/TokenThenValue", func(t *testing.T) { - enc.s.reset(enc.s.Buf[:0], nil) - checkWriteToken(t, BeginArray, nil) - checkWriteValue(t, trimArray(maxArrays), wantErr) - checkWriteValue(t, trimArray(trimArray(maxArrays)), nil) - checkWriteToken(t, EndArray, nil) - }) - t.Run("Arrays/AllTokens", func(t *testing.T) { - enc.s.reset(enc.s.Buf[:0], nil) - for range maxNestingDepth { - checkWriteToken(t, BeginArray, nil) - } - checkWriteToken(t, BeginArray, wantErr) - for range maxNestingDepth { - checkWriteToken(t, EndArray, nil) - } - }) - - wantErr = &SyntacticError{ - ByteOffset: maxNestingDepth * int64(len(`{"":`)), - JSONPointer: Pointer(strings.Repeat("/", maxNestingDepth)), - Err: errMaxDepth, - } - t.Run("Objects/SingleValue", func(t *testing.T) { - enc.s.reset(enc.s.Buf[:0], nil) - checkWriteValue(t, maxObjects, wantErr) - checkWriteValue(t, trimObject(maxObjects), nil) - }) - t.Run("Objects/TokenThenValue", func(t *testing.T) { - enc.s.reset(enc.s.Buf[:0], nil) - checkWriteToken(t, BeginObject, nil) - checkWriteToken(t, String(""), nil) - checkWriteValue(t, trimObject(maxObjects), wantErr) - checkWriteValue(t, trimObject(trimObject(maxObjects)), nil) - checkWriteToken(t, EndObject, nil) - }) - t.Run("Objects/AllTokens", func(t *testing.T) { - enc.s.reset(enc.s.Buf[:0], nil) - for range maxNestingDepth - 1 { - checkWriteToken(t, BeginObject, nil) - checkWriteToken(t, String(""), nil) - } - checkWriteToken(t, BeginObject, nil) - checkWriteToken(t, String(""), nil) - checkWriteToken(t, BeginObject, wantErr) - checkWriteToken(t, String(""), nil) - for range maxNestingDepth { - checkWriteToken(t, EndObject, nil) - } - }) - }) -} - -// FaultyBuffer implements io.Reader and io.Writer. -// It may process fewer bytes than the provided buffer -// and may randomly return an error. -type FaultyBuffer struct { - B []byte - - // MaxBytes is the maximum number of bytes read/written. - // A random number of bytes within [0, MaxBytes] are processed. - // A non-positive value is treated as infinity. - MaxBytes int - - // MayError specifies whether to randomly provide this error. - // Even if an error is returned, no bytes are dropped. - MayError error - - // Rand to use for pseudo-random behavior. - // If nil, it will be initialized with rand.NewSource(0). - Rand rand.Source -} - -func (p *FaultyBuffer) Read(b []byte) (int, error) { - b = b[:copy(b[:p.mayTruncate(len(b))], p.B)] - p.B = p.B[len(b):] - if len(p.B) == 0 && (len(b) == 0 || p.randN(2) == 0) { - return len(b), io.EOF - } - return len(b), p.mayError() -} - -func (p *FaultyBuffer) Write(b []byte) (int, error) { - b2 := b[:p.mayTruncate(len(b))] - p.B = append(p.B, b2...) - if len(b2) < len(b) { - return len(b2), io.ErrShortWrite - } - return len(b2), p.mayError() -} - -// mayTruncate may return a value between [0, n]. -func (p *FaultyBuffer) mayTruncate(n int) int { - if p.MaxBytes > 0 { - if n > p.MaxBytes { - n = p.MaxBytes - } - return p.randN(n + 1) - } - return n -} - -// mayError may return a non-nil error. -func (p *FaultyBuffer) mayError() error { - if p.MayError != nil && p.randN(2) == 0 { - return p.MayError - } - return nil -} - -func (p *FaultyBuffer) randN(n int) int { - if p.Rand == nil { - p.Rand = rand.NewSource(0) - } - return int(p.Rand.Int63() % int64(n)) -} diff --git a/pkg/encoders/json/jsontext/decode_test.go b/pkg/encoders/json/jsontext/decode_test.go deleted file mode 100644 index 67580e6..0000000 --- a/pkg/encoders/json/jsontext/decode_test.go +++ /dev/null @@ -1,1267 +0,0 @@ -// Copyright 2020 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. - -//go:build goexperiment.jsonv2 - -package jsontext - -import ( - "bytes" - "errors" - "fmt" - "io" - "net" - "path" - "reflect" - "slices" - "strings" - "testing" - "testing/iotest" - - "encoding/json/internal/jsonflags" - "encoding/json/internal/jsontest" - "encoding/json/internal/jsonwire" -) - -// equalTokens reports whether to sequences of tokens formats the same way. -func equalTokens(xs, ys []Token) bool { - if len(xs) != len(ys) { - return false - } - for i := range xs { - if !(reflect.DeepEqual(xs[i], ys[i]) || xs[i].String() == ys[i].String()) { - return false - } - } - return true -} - -// TestDecoder tests whether we can parse JSON with either tokens or raw values. -func TestDecoder(t *testing.T) { - for _, td := range coderTestdata { - for _, typeName := range []string{"Token", "Value", "TokenDelims"} { - t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) { - testDecoder(t, td.name.Where, typeName, td) - }) - } - } -} -func testDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) { - dec := NewDecoder(bytes.NewBufferString(td.in)) - switch typeName { - case "Token": - var tokens []Token - var pointers []Pointer - for { - tok, err := dec.ReadToken() - if err != nil { - if err == io.EOF { - break - } - t.Fatalf("%s: Decoder.ReadToken error: %v", where, err) - } - tokens = append(tokens, tok.Clone()) - if td.pointers != nil { - pointers = append(pointers, dec.StackPointer()) - } - } - if !equalTokens(tokens, td.tokens) { - t.Fatalf("%s: tokens mismatch:\ngot %v\nwant %v", where, tokens, td.tokens) - } - if !slices.Equal(pointers, td.pointers) { - t.Fatalf("%s: pointers mismatch:\ngot %q\nwant %q", where, pointers, td.pointers) - } - case "Value": - val, err := dec.ReadValue() - if err != nil { - t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) - } - got := string(val) - want := strings.TrimSpace(td.in) - if got != want { - t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want) - } - case "TokenDelims": - // Use ReadToken for object/array delimiters, ReadValue otherwise. - var tokens []Token - loop: - for { - switch dec.PeekKind() { - case '{', '}', '[', ']': - tok, err := dec.ReadToken() - if err != nil { - if err == io.EOF { - break loop - } - t.Fatalf("%s: Decoder.ReadToken error: %v", where, err) - } - tokens = append(tokens, tok.Clone()) - default: - val, err := dec.ReadValue() - if err != nil { - if err == io.EOF { - break loop - } - t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) - } - tokens = append(tokens, rawToken(string(val))) - } - } - if !equalTokens(tokens, td.tokens) { - t.Fatalf("%s: tokens mismatch:\ngot %v\nwant %v", where, tokens, td.tokens) - } - } -} - -// TestFaultyDecoder tests that temporary I/O errors are not fatal. -func TestFaultyDecoder(t *testing.T) { - for _, td := range coderTestdata { - for _, typeName := range []string{"Token", "Value"} { - t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) { - testFaultyDecoder(t, td.name.Where, typeName, td) - }) - } - } -} -func testFaultyDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) { - b := &FaultyBuffer{ - B: []byte(td.in), - MaxBytes: 1, - MayError: io.ErrNoProgress, - } - - // Read all the tokens. - // If the underlying io.Reader is faulty, then Read may return - // an error without changing the internal state machine. - // In other words, I/O errors occur before syntactic errors. - dec := NewDecoder(b) - switch typeName { - case "Token": - var tokens []Token - for { - tok, err := dec.ReadToken() - if err != nil { - if err == io.EOF { - break - } - if !errors.Is(err, io.ErrNoProgress) { - t.Fatalf("%s: %d: Decoder.ReadToken error: %v", where, len(tokens), err) - } - continue - } - tokens = append(tokens, tok.Clone()) - } - if !equalTokens(tokens, td.tokens) { - t.Fatalf("%s: tokens mismatch:\ngot %s\nwant %s", where, tokens, td.tokens) - } - case "Value": - for { - val, err := dec.ReadValue() - if err != nil { - if err == io.EOF { - break - } - if !errors.Is(err, io.ErrNoProgress) { - t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) - } - continue - } - got := string(val) - want := strings.TrimSpace(td.in) - if got != want { - t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want) - } - } - } -} - -type decoderMethodCall struct { - wantKind Kind - wantOut tokOrVal - wantErr error - wantPointer Pointer -} - -var decoderErrorTestdata = []struct { - name jsontest.CaseName - opts []Options - in string - calls []decoderMethodCall - wantOffset int -}{{ - name: jsontest.Name("InvalidStart"), - in: ` #`, - calls: []decoderMethodCall{ - {'#', zeroToken, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""}, - {'#', zeroValue, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""}, - }, -}, { - name: jsontest.Name("StreamN0"), - in: ` `, - calls: []decoderMethodCall{ - {0, zeroToken, io.EOF, ""}, - {0, zeroValue, io.EOF, ""}, - }, -}, { - name: jsontest.Name("StreamN1"), - in: ` null `, - calls: []decoderMethodCall{ - {'n', Null, nil, ""}, - {0, zeroToken, io.EOF, ""}, - {0, zeroValue, io.EOF, ""}, - }, - wantOffset: len(` null`), -}, { - name: jsontest.Name("StreamN2"), - in: ` nullnull `, - calls: []decoderMethodCall{ - {'n', Null, nil, ""}, - {'n', Null, nil, ""}, - {0, zeroToken, io.EOF, ""}, - {0, zeroValue, io.EOF, ""}, - }, - wantOffset: len(` nullnull`), -}, { - name: jsontest.Name("StreamN2/ExtraComma"), // stream is whitespace delimited, not comma delimited - in: ` null , null `, - calls: []decoderMethodCall{ - {'n', Null, nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""}, - }, - wantOffset: len(` null`), -}, { - name: jsontest.Name("TruncatedNull"), - in: `nul`, - calls: []decoderMethodCall{ - {'n', zeroToken, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""}, - {'n', zeroValue, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidNull"), - in: `nulL`, - calls: []decoderMethodCall{ - {'n', zeroToken, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""}, - {'n', zeroValue, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedFalse"), - in: `fals`, - calls: []decoderMethodCall{ - {'f', zeroToken, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""}, - {'f', zeroValue, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidFalse"), - in: `falsE`, - calls: []decoderMethodCall{ - {'f', zeroToken, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""}, - {'f', zeroValue, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedTrue"), - in: `tru`, - calls: []decoderMethodCall{ - {'t', zeroToken, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""}, - {'t', zeroValue, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidTrue"), - in: `truE`, - calls: []decoderMethodCall{ - {'t', zeroToken, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""}, - {'t', zeroValue, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedString"), - in: `"start`, - calls: []decoderMethodCall{ - {'"', zeroToken, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""}, - {'"', zeroValue, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidString"), - in: `"ok` + "\x00", - calls: []decoderMethodCall{ - {'"', zeroToken, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""}, - {'"', zeroValue, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""}, - }, -}, { - name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"), - opts: []Options{AllowInvalidUTF8(true)}, - in: "\"living\xde\xad\xbe\xef\"", - calls: []decoderMethodCall{ - {'"', rawToken("\"living\xde\xad\xbe\xef\""), nil, ""}, - }, - wantOffset: len("\"living\xde\xad\xbe\xef\""), -}, { - name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"), - opts: []Options{AllowInvalidUTF8(true)}, - in: "\"living\xde\xad\xbe\xef\"", - calls: []decoderMethodCall{ - {'"', Value("\"living\xde\xad\xbe\xef\""), nil, ""}, - }, - wantOffset: len("\"living\xde\xad\xbe\xef\""), -}, { - name: jsontest.Name("InvalidString/RejectInvalidUTF8"), - opts: []Options{AllowInvalidUTF8(false)}, - in: "\"living\xde\xad\xbe\xef\"", - calls: []decoderMethodCall{ - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedNumber"), - in: `0.`, - calls: []decoderMethodCall{ - {'0', zeroToken, E(io.ErrUnexpectedEOF), ""}, - {'0', zeroValue, E(io.ErrUnexpectedEOF), ""}, - }, -}, { - name: jsontest.Name("InvalidNumber"), - in: `0.e`, - calls: []decoderMethodCall{ - {'0', zeroToken, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""}, - {'0', zeroValue, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedObject/AfterStart"), - in: `{`, - calls: []decoderMethodCall{ - {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, - {'{', BeginObject, nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, - }, - wantOffset: len(`{`), -}, { - name: jsontest.Name("TruncatedObject/AfterName"), - in: `{"0"`, - calls: []decoderMethodCall{ - {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("0"), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, - }, - wantOffset: len(`{"0"`), -}, { - name: jsontest.Name("TruncatedObject/AfterColon"), - in: `{"0":`, - calls: []decoderMethodCall{ - {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("0"), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, - }, - wantOffset: len(`{"0"`), -}, { - name: jsontest.Name("TruncatedObject/AfterValue"), - in: `{"0":0`, - calls: []decoderMethodCall{ - {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("0"), nil, ""}, - {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, - }, - wantOffset: len(`{"0":0`), -}, { - name: jsontest.Name("TruncatedObject/AfterComma"), - in: `{"0":0,`, - calls: []decoderMethodCall{ - {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("0"), nil, ""}, - {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, - }, - wantOffset: len(`{"0":0`), -}, { - name: jsontest.Name("InvalidObject/MissingColon"), - in: ` { "fizz" "buzz" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {0, zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - }, - wantOffset: len(` { "fizz"`), -}, { - name: jsontest.Name("InvalidObject/MissingColon/GotComma"), - in: ` { "fizz" , "buzz" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - }, - wantOffset: len(` { "fizz"`), -}, { - name: jsontest.Name("InvalidObject/MissingColon/GotHash"), - in: ` { "fizz" # "buzz" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {0, zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - }, - wantOffset: len(` { "fizz"`), -}, { - name: jsontest.Name("InvalidObject/MissingComma"), - in: ` { "fizz" : "buzz" "gazz" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("fizz"), nil, ""}, - {'"', String("buzz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - {0, zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - }, - wantOffset: len(` { "fizz" : "buzz"`), -}, { - name: jsontest.Name("InvalidObject/MissingComma/GotColon"), - in: ` { "fizz" : "buzz" : "gazz" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("fizz"), nil, ""}, - {'"', String("buzz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - {0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - }, - wantOffset: len(` { "fizz" : "buzz"`), -}, { - name: jsontest.Name("InvalidObject/MissingComma/GotHash"), - in: ` { "fizz" : "buzz" # "gazz" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("fizz"), nil, ""}, - {'"', String("buzz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - {0, zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - }, - wantOffset: len(` { "fizz" : "buzz"`), -}, { - name: jsontest.Name("InvalidObject/ExtraComma/AfterStart"), - in: ` { , } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("InvalidObject/ExtraComma/AfterValue"), - in: ` { "fizz" : "buzz" , } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("fizz"), nil, ""}, - {'"', String("buzz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""}, - }, - wantOffset: len(` { "fizz" : "buzz"`), -}, { - name: jsontest.Name("InvalidObject/InvalidName/GotNull"), - in: ` { null : null } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("n", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'n', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, - {'n', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("InvalidObject/InvalidName/GotFalse"), - in: ` { false : false } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("f", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'f', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, - {'f', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("InvalidObject/InvalidName/GotTrue"), - in: ` { true : true } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("t", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'t', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, - {'t', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("InvalidObject/InvalidName/GotNumber"), - in: ` { 0 : 0 } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("0", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'0', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, - {'0', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("InvalidObject/InvalidName/GotObject"), - in: ` { {} : {} } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("{", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'{', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, - {'{', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("InvalidObject/InvalidName/GotArray"), - in: ` { [] : [] } `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("[", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {'[', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, - {'[', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("InvalidObject/MismatchingDelim"), - in: ` { ] `, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError("]", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, - {'{', BeginObject, nil, ""}, - {']', zeroToken, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""}, - {']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("ValidObject/InvalidValue"), - in: ` { } `, - calls: []decoderMethodCall{ - {'{', BeginObject, nil, ""}, - {'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(" { ", ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("ValidObject/UniqueNames"), - in: `{"0":0,"1":1} `, - calls: []decoderMethodCall{ - {'{', BeginObject, nil, ""}, - {'"', String("0"), nil, ""}, - {'0', Uint(0), nil, ""}, - {'"', String("1"), nil, ""}, - {'0', Uint(1), nil, ""}, - {'}', EndObject, nil, ""}, - }, - wantOffset: len(`{"0":0,"1":1}`), -}, { - name: jsontest.Name("ValidObject/DuplicateNames"), - opts: []Options{AllowDuplicateNames(true)}, - in: `{"0":0,"0":0} `, - calls: []decoderMethodCall{ - {'{', BeginObject, nil, ""}, - {'"', String("0"), nil, ""}, - {'0', Uint(0), nil, ""}, - {'"', String("0"), nil, ""}, - {'0', Uint(0), nil, ""}, - {'}', EndObject, nil, ""}, - }, - wantOffset: len(`{"0":0,"0":0}`), -}, { - name: jsontest.Name("InvalidObject/DuplicateNames"), - in: `{"X":{},"Y":{},"X":{}} `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("X"), nil, ""}, - {'{', BeginObject, nil, ""}, - {'}', EndObject, nil, ""}, - {'"', String("Y"), nil, ""}, - {'{', BeginObject, nil, ""}, - {'}', EndObject, nil, ""}, - {'"', zeroToken, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"}, - {'"', zeroValue, E(ErrDuplicateName).withPos(`{"0":{},"Y":{},`, "/X"), "/Y"}, - }, - wantOffset: len(`{"0":{},"1":{}`), -}, { - name: jsontest.Name("TruncatedArray/AfterStart"), - in: `[`, - calls: []decoderMethodCall{ - {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, - {'[', BeginArray, nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, - }, - wantOffset: len(`[`), -}, { - name: jsontest.Name("TruncatedArray/AfterValue"), - in: `[0`, - calls: []decoderMethodCall{ - {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, - {'[', BeginArray, nil, ""}, - {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, - }, - wantOffset: len(`[0`), -}, { - name: jsontest.Name("TruncatedArray/AfterComma"), - in: `[0,`, - calls: []decoderMethodCall{ - {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, - {'[', BeginArray, nil, ""}, - {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, - }, - wantOffset: len(`[0`), -}, { - name: jsontest.Name("InvalidArray/MissingComma"), - in: ` [ "fizz" "buzz" ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, - {'[', BeginArray, nil, ""}, - {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, - {0, zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, - }, - wantOffset: len(` [ "fizz"`), -}, { - name: jsontest.Name("InvalidArray/MismatchingDelim"), - in: ` [ } `, - calls: []decoderMethodCall{ - {'[', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""}, - {'[', BeginArray, nil, ""}, - {'}', zeroToken, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""}, - {'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""}, - }, - wantOffset: len(` [`), -}, { - name: jsontest.Name("ValidArray/InvalidValue"), - in: ` [ ] `, - calls: []decoderMethodCall{ - {'[', BeginArray, nil, ""}, - {']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(" [ ", "/0"), ""}, - }, - wantOffset: len(` [`), -}, { - name: jsontest.Name("InvalidDelim/AfterTopLevel"), - in: `"",`, - calls: []decoderMethodCall{ - {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, - }, - wantOffset: len(`""`), -}, { - name: jsontest.Name("InvalidDelim/AfterBeginObject"), - in: `{:`, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError(":", `at start of string (expecting '"')`).withPos(`{`, ""), ""}, - {'{', BeginObject, nil, ""}, - {0, zeroToken, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, - }, - wantOffset: len(`{`), -}, { - name: jsontest.Name("InvalidDelim/AfterObjectName"), - in: `{"",`, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, - {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, - }, - wantOffset: len(`{""`), -}, { - name: jsontest.Name("ValidDelim/AfterObjectName"), - in: `{"":`, - calls: []decoderMethodCall{ - {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String(""), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, - }, - wantOffset: len(`{""`), -}, { - name: jsontest.Name("InvalidDelim/AfterObjectValue"), - in: `{"":"":`, - calls: []decoderMethodCall{ - {'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String(""), nil, ""}, - {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, - }, - wantOffset: len(`{"":""`), -}, { - name: jsontest.Name("ValidDelim/AfterObjectValue"), - in: `{"":"",`, - calls: []decoderMethodCall{ - {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String(""), nil, ""}, - {'"', String(""), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, - }, - wantOffset: len(`{"":""`), -}, { - name: jsontest.Name("InvalidDelim/AfterBeginArray"), - in: `[,`, - calls: []decoderMethodCall{ - {'[', zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, "/0"), ""}, - {'[', BeginArray, nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, - }, - wantOffset: len(`[`), -}, { - name: jsontest.Name("InvalidDelim/AfterArrayValue"), - in: `["":`, - calls: []decoderMethodCall{ - {'[', zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, - {'[', BeginArray, nil, ""}, - {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, - }, - wantOffset: len(`[""`), -}, { - name: jsontest.Name("ValidDelim/AfterArrayValue"), - in: `["",`, - calls: []decoderMethodCall{ - {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, - {'[', BeginArray, nil, ""}, - {'"', String(""), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, - }, - wantOffset: len(`[""`), -}, { - name: jsontest.Name("ErrorPosition"), - in: ` "a` + "\xff" + `0" `, - calls: []decoderMethodCall{ - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""}, - }, -}, { - name: jsontest.Name("ErrorPosition/0"), - in: ` [ "a` + "\xff" + `1" ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, - {'[', BeginArray, nil, ""}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, - }, - wantOffset: len(` [`), -}, { - name: jsontest.Name("ErrorPosition/1"), - in: ` [ "a1" , "b` + "\xff" + `1" ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, - {'[', BeginArray, nil, ""}, - {'"', String("a1"), nil, ""}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, - }, - wantOffset: len(` [ "a1"`), -}, { - name: jsontest.Name("ErrorPosition/0/0"), - in: ` [ [ "a` + "\xff" + `2" ] ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, - {'[', BeginArray, nil, ""}, - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, - {'[', BeginArray, nil, "/0"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, - }, - wantOffset: len(` [ [`), -}, { - name: jsontest.Name("ErrorPosition/1/0"), - in: ` [ "a1" , [ "a` + "\xff" + `2" ] ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""}, - {'[', BeginArray, nil, ""}, - {'"', String("a1"), nil, "/0"}, - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/0"}, - {'[', BeginArray, nil, "/1"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"}, - }, - wantOffset: len(` [ "a1" , [`), -}, { - name: jsontest.Name("ErrorPosition/0/1"), - in: ` [ [ "a2" , "b` + "\xff" + `2" ] ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""}, - {'[', BeginArray, nil, ""}, - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""}, - {'[', BeginArray, nil, "/0"}, - {'"', String("a2"), nil, "/0/0"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"}, - }, - wantOffset: len(` [ [ "a2"`), -}, { - name: jsontest.Name("ErrorPosition/1/1"), - in: ` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""}, - {'[', BeginArray, nil, ""}, - {'"', String("a1"), nil, "/0"}, - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""}, - {'[', BeginArray, nil, "/1"}, - {'"', String("a2"), nil, "/1/0"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"}, - }, - wantOffset: len(` [ "a1" , [ "a2"`), -}, { - name: jsontest.Name("ErrorPosition/a1-"), - in: ` { "a` + "\xff" + `1" : "b1" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, - }, - wantOffset: len(` {`), -}, { - name: jsontest.Name("ErrorPosition/a1"), - in: ` { "a1" : "b` + "\xff" + `1" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, - }, - wantOffset: len(` { "a1"`), -}, { - name: jsontest.Name("ErrorPosition/c1-"), - in: ` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'"', String("b1"), nil, "/a1"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"}, - }, - wantOffset: len(` { "a1" : "b1"`), -}, { - name: jsontest.Name("ErrorPosition/c1"), - in: ` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'"', String("b1"), nil, "/a1"}, - {'"', String("c1"), nil, "/c1"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"}, - }, - wantOffset: len(` { "a1" : "b1" , "c1"`), -}, { - name: jsontest.Name("ErrorPosition/a1/a2-"), - in: ` { "a1" : { "a` + "\xff" + `2" : "b2" } } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""}, - {'{', BeginObject, nil, "/a1"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"}, - }, - wantOffset: len(` { "a1" : {`), -}, { - name: jsontest.Name("ErrorPosition/a1/a2"), - in: ` { "a1" : { "a2" : "b` + "\xff" + `2" } } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""}, - {'{', BeginObject, nil, "/a1"}, - {'"', String("a2"), nil, "/a1/a2"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"}, - }, - wantOffset: len(` { "a1" : { "a2"`), -}, { - name: jsontest.Name("ErrorPosition/a1/c2-"), - in: ` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'{', BeginObject, nil, "/a1"}, - {'"', String("a2"), nil, "/a1/a2"}, - {'"', String("b2"), nil, "/a1/a2"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"}, - }, - wantOffset: len(` { "a1" : { "a2" : "b2"`), -}, { - name: jsontest.Name("ErrorPosition/a1/c2"), - in: ` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a2"), nil, "/a1/a2"}, - {'"', String("b2"), nil, "/a1/a2"}, - {'"', String("c2"), nil, "/a1/c2"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"}, - }, - wantOffset: len(` { "a1" : { "a2" : "b2" , "c2"`), -}, { - name: jsontest.Name("ErrorPosition/1/a2"), - in: ` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""}, - {'[', BeginArray, nil, ""}, - {'"', String("a1"), nil, "/0"}, - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""}, - {'{', BeginObject, nil, "/1"}, - {'"', String("a2"), nil, "/1/a2"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"}, - }, - wantOffset: len(` [ "a1" , { "a2"`), -}, { - name: jsontest.Name("ErrorPosition/c1/1"), - in: ` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `, - calls: []decoderMethodCall{ - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""}, - {'{', BeginObject, nil, ""}, - {'"', String("a1"), nil, "/a1"}, - {'"', String("b1"), nil, "/a1"}, - {'"', String("c1"), nil, "/c1"}, - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""}, - {'[', BeginArray, nil, "/c1"}, - {'"', String("a2"), nil, "/c1/0"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"}, - }, - wantOffset: len(` { "a1" : "b1" , "c1" : [ "a2"`), -}, { - name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"), - in: ` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `, - calls: []decoderMethodCall{ - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {'[', BeginArray, nil, ""}, - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {'{', BeginObject, nil, "/0"}, - {'"', String("a1"), nil, "/0/a1"}, - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {'[', BeginArray, nil, ""}, - {'"', String("a2"), nil, "/0/a1/0"}, - {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {'{', BeginObject, nil, "/0/a1/1"}, - {'"', String("a3"), nil, "/0/a1/1/a3"}, - {'"', String("b3"), nil, "/0/a1/1/a3"}, - {'"', String("c3"), nil, "/0/a1/1/c3"}, - {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {'[', BeginArray, nil, "/0/a1/1/c3"}, - {'"', String("a4"), nil, "/0/a1/1/c3/0"}, - {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, - {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, - }, - wantOffset: len(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4"`), -}} - -// TestDecoderErrors test that Decoder errors occur when we expect and -// leaves the Decoder in a consistent state. -func TestDecoderErrors(t *testing.T) { - for _, td := range decoderErrorTestdata { - t.Run(path.Join(td.name.Name), func(t *testing.T) { - testDecoderErrors(t, td.name.Where, td.opts, td.in, td.calls, td.wantOffset) - }) - } -} -func testDecoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, in string, calls []decoderMethodCall, wantOffset int) { - src := bytes.NewBufferString(in) - dec := NewDecoder(src, opts...) - for i, call := range calls { - gotKind := dec.PeekKind() - if gotKind != call.wantKind { - t.Fatalf("%s: %d: Decoder.PeekKind = %v, want %v", where, i, gotKind, call.wantKind) - } - - var gotErr error - switch wantOut := call.wantOut.(type) { - case Token: - var gotOut Token - gotOut, gotErr = dec.ReadToken() - if gotOut.String() != wantOut.String() { - t.Fatalf("%s: %d: Decoder.ReadToken = %v, want %v", where, i, gotOut, wantOut) - } - case Value: - var gotOut Value - gotOut, gotErr = dec.ReadValue() - if string(gotOut) != string(wantOut) { - t.Fatalf("%s: %d: Decoder.ReadValue = %s, want %s", where, i, gotOut, wantOut) - } - } - if !equalError(gotErr, call.wantErr) { - t.Fatalf("%s: %d: error mismatch:\ngot %v\nwant %v", where, i, gotErr, call.wantErr) - } - if call.wantPointer != "" { - gotPointer := dec.StackPointer() - if gotPointer != call.wantPointer { - t.Fatalf("%s: %d: Decoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer) - } - } - } - gotOffset := int(dec.InputOffset()) - if gotOffset != wantOffset { - t.Fatalf("%s: Decoder.InputOffset = %v, want %v", where, gotOffset, wantOffset) - } - gotUnread := string(dec.s.unreadBuffer()) // should be a prefix of wantUnread - wantUnread := in[wantOffset:] - if !strings.HasPrefix(wantUnread, gotUnread) { - t.Fatalf("%s: Decoder.UnreadBuffer = %v, want %v", where, gotUnread, wantUnread) - } -} - -// TestBufferDecoder tests that we detect misuses of bytes.Buffer with Decoder. -func TestBufferDecoder(t *testing.T) { - bb := bytes.NewBufferString("[null, false, true]") - dec := NewDecoder(bb) - var err error - for { - if _, err = dec.ReadToken(); err != nil { - break - } - bb.WriteByte(' ') // not allowed to write to the buffer while reading - } - want := &ioError{action: "read", err: errBufferWriteAfterNext} - if !equalError(err, want) { - t.Fatalf("error mismatch: got %v, want %v", err, want) - } -} - -var resumableDecoderTestdata = []string{ - `0`, - `123456789`, - `0.0`, - `0.123456789`, - `0e0`, - `0e+0`, - `0e123456789`, - `0e+123456789`, - `123456789.123456789e+123456789`, - `-0`, - `-123456789`, - `-0.0`, - `-0.123456789`, - `-0e0`, - `-0e-0`, - `-0e123456789`, - `-0e-123456789`, - `-123456789.123456789e-123456789`, - - `""`, - `"a"`, - `"ab"`, - `"abc"`, - `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, - `"\"\\\/\b\f\n\r\t"`, - `"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, - `"\ud800\udead"`, - "\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", - `"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`, -} - -// TestResumableDecoder tests that resume logic for parsing a -// JSON string and number properly works across every possible split point. -func TestResumableDecoder(t *testing.T) { - for _, want := range resumableDecoderTestdata { - t.Run("", func(t *testing.T) { - dec := NewDecoder(iotest.OneByteReader(strings.NewReader(want))) - got, err := dec.ReadValue() - if err != nil { - t.Fatalf("Decoder.ReadValue error: %v", err) - } - if string(got) != want { - t.Fatalf("Decoder.ReadValue = %s, want %s", got, want) - } - }) - } -} - -// TestBlockingDecoder verifies that JSON values except numbers can be -// synchronously sent and received on a blocking pipe without a deadlock. -// Numbers are the exception since termination cannot be determined until -// either the pipe ends or a non-numeric character is encountered. -func TestBlockingDecoder(t *testing.T) { - values := []string{"null", "false", "true", `""`, `{}`, `[]`} - - r, w := net.Pipe() - defer r.Close() - defer w.Close() - - enc := NewEncoder(w, jsonflags.OmitTopLevelNewline|1) - dec := NewDecoder(r) - - errCh := make(chan error) - - // Test synchronous ReadToken calls. - for _, want := range values { - go func() { - errCh <- enc.WriteValue(Value(want)) - }() - - tok, err := dec.ReadToken() - if err != nil { - t.Fatalf("Decoder.ReadToken error: %v", err) - } - got := tok.String() - switch tok.Kind() { - case '"': - got = `"` + got + `"` - case '{', '[': - tok, err := dec.ReadToken() - if err != nil { - t.Fatalf("Decoder.ReadToken error: %v", err) - } - got += tok.String() - } - if got != want { - t.Fatalf("ReadTokens = %s, want %s", got, want) - } - - if err := <-errCh; err != nil { - t.Fatalf("Encoder.WriteValue error: %v", err) - } - } - - // Test synchronous ReadValue calls. - for _, want := range values { - go func() { - errCh <- enc.WriteValue(Value(want)) - }() - - got, err := dec.ReadValue() - if err != nil { - t.Fatalf("Decoder.ReadValue error: %v", err) - } - if string(got) != want { - t.Fatalf("ReadValue = %s, want %s", got, want) - } - - if err := <-errCh; err != nil { - t.Fatalf("Encoder.WriteValue error: %v", err) - } - } -} - -func TestPeekableDecoder(t *testing.T) { - type operation any // PeekKind | ReadToken | ReadValue | BufferWrite - type PeekKind struct { - want Kind - } - type ReadToken struct { - wantKind Kind - wantErr error - } - type ReadValue struct { - wantKind Kind - wantErr error - } - type WriteString struct { - in string - } - ops := []operation{ - PeekKind{0}, - WriteString{"[ "}, - ReadToken{0, io.EOF}, // previous error from PeekKind is cached once - ReadToken{'[', nil}, - - PeekKind{0}, - WriteString{"] "}, - ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ", "")}, // previous error from PeekKind is cached once - ReadValue{0, newInvalidCharacterError("]", "at start of value").withPos("[ ", "/0")}, - ReadToken{']', nil}, - - WriteString{"[ "}, - ReadToken{'[', nil}, - - WriteString{" null "}, - PeekKind{'n'}, - PeekKind{'n'}, - ReadToken{'n', nil}, - - WriteString{", "}, - PeekKind{0}, - WriteString{"fal"}, - PeekKind{'f'}, - ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , fal", "/1")}, - WriteString{"se "}, - ReadValue{'f', nil}, - - PeekKind{0}, - WriteString{" , "}, - PeekKind{0}, - WriteString{` "" `}, - ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , false , ", "")}, // previous error from PeekKind is cached once - ReadValue{'"', nil}, - - WriteString{" , 0"}, - PeekKind{'0'}, - ReadToken{'0', nil}, - - WriteString{" , {} , []"}, - PeekKind{'{'}, - ReadValue{'{', nil}, - ReadValue{'[', nil}, - - WriteString{"]"}, - ReadToken{']', nil}, - } - - bb := struct{ *bytes.Buffer }{new(bytes.Buffer)} - d := NewDecoder(bb) - for i, op := range ops { - switch op := op.(type) { - case PeekKind: - if got := d.PeekKind(); got != op.want { - t.Fatalf("%d: Decoder.PeekKind() = %v, want %v", i, got, op.want) - } - case ReadToken: - gotTok, gotErr := d.ReadToken() - gotKind := gotTok.Kind() - if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) { - t.Fatalf("%d: Decoder.ReadToken() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr) - } - case ReadValue: - gotVal, gotErr := d.ReadValue() - gotKind := gotVal.Kind() - if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) { - t.Fatalf("%d: Decoder.ReadValue() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr) - } - case WriteString: - bb.WriteString(op.in) - default: - panic(fmt.Sprintf("unknown operation: %T", op)) - } - } -} diff --git a/pkg/encoders/json/jsontext/encode_test.go b/pkg/encoders/json/jsontext/encode_test.go deleted file mode 100644 index 2064822..0000000 --- a/pkg/encoders/json/jsontext/encode_test.go +++ /dev/null @@ -1,737 +0,0 @@ -// Copyright 2020 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. - -//go:build goexperiment.jsonv2 - -package jsontext - -import ( - "bytes" - "errors" - "io" - "path" - "slices" - "testing" - - "encoding/json/internal/jsonflags" - "encoding/json/internal/jsontest" - "encoding/json/internal/jsonwire" -) - -// TestEncoder tests whether we can produce JSON with either tokens or raw values. -func TestEncoder(t *testing.T) { - for _, td := range coderTestdata { - for _, formatName := range []string{"Compact", "Indented"} { - for _, typeName := range []string{"Token", "Value", "TokenDelims"} { - t.Run(path.Join(td.name.Name, typeName, formatName), func(t *testing.T) { - testEncoder(t, td.name.Where, formatName, typeName, td) - }) - } - } - } -} -func testEncoder(t *testing.T, where jsontest.CasePos, formatName, typeName string, td coderTestdataEntry) { - var want string - var opts []Options - dst := new(bytes.Buffer) - opts = append(opts, jsonflags.OmitTopLevelNewline|1) - want = td.outCompacted - switch formatName { - case "Indented": - opts = append(opts, Multiline(true)) - opts = append(opts, WithIndentPrefix("\t")) - opts = append(opts, WithIndent(" ")) - if td.outIndented != "" { - want = td.outIndented - } - } - enc := NewEncoder(dst, opts...) - - switch typeName { - case "Token": - var pointers []Pointer - for _, tok := range td.tokens { - if err := enc.WriteToken(tok); err != nil { - t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) - } - if td.pointers != nil { - pointers = append(pointers, enc.StackPointer()) - } - } - if !slices.Equal(pointers, td.pointers) { - t.Fatalf("%s: pointers mismatch:\ngot %q\nwant %q", where, pointers, td.pointers) - } - case "Value": - if err := enc.WriteValue(Value(td.in)); err != nil { - t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) - } - case "TokenDelims": - // Use WriteToken for object/array delimiters, WriteValue otherwise. - for _, tok := range td.tokens { - switch tok.Kind() { - case '{', '}', '[', ']': - if err := enc.WriteToken(tok); err != nil { - t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) - } - default: - val := Value(tok.String()) - if tok.Kind() == '"' { - val, _ = jsonwire.AppendQuote(nil, tok.String(), &jsonflags.Flags{}) - } - if err := enc.WriteValue(val); err != nil { - t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) - } - } - } - } - - got := dst.String() - if got != want { - t.Errorf("%s: output mismatch:\ngot %q\nwant %q", where, got, want) - } -} - -// TestFaultyEncoder tests that temporary I/O errors are not fatal. -func TestFaultyEncoder(t *testing.T) { - for _, td := range coderTestdata { - for _, typeName := range []string{"Token", "Value"} { - t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) { - testFaultyEncoder(t, td.name.Where, typeName, td) - }) - } - } -} -func testFaultyEncoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) { - b := &FaultyBuffer{ - MaxBytes: 1, - MayError: io.ErrShortWrite, - } - - // Write all the tokens. - // Even if the underlying io.Writer may be faulty, - // writing a valid token or value is guaranteed to at least - // be appended to the internal buffer. - // In other words, syntactic errors occur before I/O errors. - enc := NewEncoder(b) - switch typeName { - case "Token": - for i, tok := range td.tokens { - err := enc.WriteToken(tok) - if err != nil && !errors.Is(err, io.ErrShortWrite) { - t.Fatalf("%s: %d: Encoder.WriteToken error: %v", where, i, err) - } - } - case "Value": - err := enc.WriteValue(Value(td.in)) - if err != nil && !errors.Is(err, io.ErrShortWrite) { - t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) - } - } - gotOutput := string(append(b.B, enc.s.unflushedBuffer()...)) - wantOutput := td.outCompacted + "\n" - if gotOutput != wantOutput { - t.Fatalf("%s: output mismatch:\ngot %s\nwant %s", where, gotOutput, wantOutput) - } -} - -type encoderMethodCall struct { - in tokOrVal - wantErr error - wantPointer Pointer -} - -var encoderErrorTestdata = []struct { - name jsontest.CaseName - opts []Options - calls []encoderMethodCall - wantOut string -}{{ - name: jsontest.Name("InvalidToken"), - calls: []encoderMethodCall{ - {zeroToken, E(errInvalidToken), ""}, - }, -}, { - name: jsontest.Name("InvalidValue"), - calls: []encoderMethodCall{ - {Value(`#`), newInvalidCharacterError("#", "at start of value"), ""}, - }, -}, { - name: jsontest.Name("InvalidValue/DoubleZero"), - calls: []encoderMethodCall{ - {Value(`00`), newInvalidCharacterError("0", "after top-level value").withPos(`0`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedValue"), - calls: []encoderMethodCall{ - {zeroValue, E(io.ErrUnexpectedEOF).withPos("", ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedNull"), - calls: []encoderMethodCall{ - {Value(`nul`), E(io.ErrUnexpectedEOF).withPos("nul", ""), ""}, - }, -}, { - name: jsontest.Name("InvalidNull"), - calls: []encoderMethodCall{ - {Value(`nulL`), newInvalidCharacterError("L", "in literal null (expecting 'l')").withPos(`nul`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedFalse"), - calls: []encoderMethodCall{ - {Value(`fals`), E(io.ErrUnexpectedEOF).withPos("fals", ""), ""}, - }, -}, { - name: jsontest.Name("InvalidFalse"), - calls: []encoderMethodCall{ - {Value(`falsE`), newInvalidCharacterError("E", "in literal false (expecting 'e')").withPos(`fals`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedTrue"), - calls: []encoderMethodCall{ - {Value(`tru`), E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidTrue"), - calls: []encoderMethodCall{ - {Value(`truE`), newInvalidCharacterError("E", "in literal true (expecting 'e')").withPos(`tru`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedString"), - calls: []encoderMethodCall{ - {Value(`"star`), E(io.ErrUnexpectedEOF).withPos(`"star`, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidString"), - calls: []encoderMethodCall{ - {Value(`"ok` + "\x00"), newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""}, - }, -}, { - name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"), - opts: []Options{AllowInvalidUTF8(true)}, - calls: []encoderMethodCall{ - {String("living\xde\xad\xbe\xef"), nil, ""}, - }, - wantOut: "\"living\xde\xad\ufffd\ufffd\"\n", -}, { - name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"), - opts: []Options{AllowInvalidUTF8(true)}, - calls: []encoderMethodCall{ - {Value("\"living\xde\xad\xbe\xef\""), nil, ""}, - }, - wantOut: "\"living\xde\xad\ufffd\ufffd\"\n", -}, { - name: jsontest.Name("InvalidString/RejectInvalidUTF8"), - opts: []Options{AllowInvalidUTF8(false)}, - calls: []encoderMethodCall{ - {String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8), ""}, - {Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""}, - {BeginObject, nil, ""}, - {String("name"), nil, ""}, - {BeginArray, nil, ""}, - {String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8).withPos(`{"name":[`, "/name/0"), ""}, - {Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("{\"name\":[\"living\xde\xad", "/name/0"), ""}, - }, - wantOut: `{"name":[`, -}, { - name: jsontest.Name("TruncatedNumber"), - calls: []encoderMethodCall{ - {Value(`0.`), E(io.ErrUnexpectedEOF).withPos("0", ""), ""}, - }, -}, { - name: jsontest.Name("InvalidNumber"), - calls: []encoderMethodCall{ - {Value(`0.e`), newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedObject/AfterStart"), - calls: []encoderMethodCall{ - {Value(`{`), E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedObject/AfterName"), - calls: []encoderMethodCall{ - {Value(`{"X"`), E(io.ErrUnexpectedEOF).withPos(`{"X"`, "/X"), ""}, - }, -}, { - name: jsontest.Name("TruncatedObject/AfterColon"), - calls: []encoderMethodCall{ - {Value(`{"X":`), E(io.ErrUnexpectedEOF).withPos(`{"X":`, "/X"), ""}, - }, -}, { - name: jsontest.Name("TruncatedObject/AfterValue"), - calls: []encoderMethodCall{ - {Value(`{"0":0`), E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedObject/AfterComma"), - calls: []encoderMethodCall{ - {Value(`{"0":0,`), E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidObject/MissingColon"), - calls: []encoderMethodCall{ - {Value(` { "fizz" "buzz" } `), newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {Value(` { "fizz" , "buzz" } `), newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - }, -}, { - name: jsontest.Name("InvalidObject/MissingComma"), - calls: []encoderMethodCall{ - {Value(` { "fizz" : "buzz" "gazz" } `), newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - {Value(` { "fizz" : "buzz" : "gazz" } `), newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidObject/ExtraComma"), - calls: []encoderMethodCall{ - {Value(` { , } `), newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""}, - {Value(` { "fizz" : "buzz" , } `), newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidObject/InvalidName"), - calls: []encoderMethodCall{ - {Value(`{ null }`), newInvalidCharacterError("n", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, - {Value(`{ false }`), newInvalidCharacterError("f", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, - {Value(`{ true }`), newInvalidCharacterError("t", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, - {Value(`{ 0 }`), newInvalidCharacterError("0", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, - {Value(`{ {} }`), newInvalidCharacterError("{", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, - {Value(`{ [] }`), newInvalidCharacterError("[", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, - {BeginObject, nil, ""}, - {Null, E(ErrNonStringName).withPos(`{`, ""), ""}, - {Value(`null`), E(ErrNonStringName).withPos(`{`, ""), ""}, - {False, E(ErrNonStringName).withPos(`{`, ""), ""}, - {Value(`false`), E(ErrNonStringName).withPos(`{`, ""), ""}, - {True, E(ErrNonStringName).withPos(`{`, ""), ""}, - {Value(`true`), E(ErrNonStringName).withPos(`{`, ""), ""}, - {Uint(0), E(ErrNonStringName).withPos(`{`, ""), ""}, - {Value(`0`), E(ErrNonStringName).withPos(`{`, ""), ""}, - {BeginObject, E(ErrNonStringName).withPos(`{`, ""), ""}, - {Value(`{}`), E(ErrNonStringName).withPos(`{`, ""), ""}, - {BeginArray, E(ErrNonStringName).withPos(`{`, ""), ""}, - {Value(`[]`), E(ErrNonStringName).withPos(`{`, ""), ""}, - {EndObject, nil, ""}, - }, - wantOut: "{}\n", -}, { - name: jsontest.Name("InvalidObject/InvalidValue"), - calls: []encoderMethodCall{ - {Value(`{ "0": x }`), newInvalidCharacterError("x", `at start of value`).withPos(`{ "0": `, "/0"), ""}, - }, -}, { - name: jsontest.Name("InvalidObject/MismatchingDelim"), - calls: []encoderMethodCall{ - {Value(` { ] `), newInvalidCharacterError("]", `at start of string (expecting '"')`).withPos(` { `, ""), ""}, - {Value(` { "0":0 ] `), newInvalidCharacterError("]", `after object value (expecting ',' or '}')`).withPos(` { "0":0 `, ""), ""}, - {BeginObject, nil, ""}, - {EndArray, E(errMismatchDelim).withPos(`{`, ""), ""}, - {Value(`]`), newInvalidCharacterError("]", "at start of value").withPos(`{`, ""), ""}, - {EndObject, nil, ""}, - }, - wantOut: "{}\n", -}, { - name: jsontest.Name("ValidObject/UniqueNames"), - calls: []encoderMethodCall{ - {BeginObject, nil, ""}, - {String("0"), nil, ""}, - {Uint(0), nil, ""}, - {String("1"), nil, ""}, - {Uint(1), nil, ""}, - {EndObject, nil, ""}, - {Value(` { "0" : 0 , "1" : 1 } `), nil, ""}, - }, - wantOut: `{"0":0,"1":1}` + "\n" + `{"0":0,"1":1}` + "\n", -}, { - name: jsontest.Name("ValidObject/DuplicateNames"), - opts: []Options{AllowDuplicateNames(true)}, - calls: []encoderMethodCall{ - {BeginObject, nil, ""}, - {String("0"), nil, ""}, - {Uint(0), nil, ""}, - {String("0"), nil, ""}, - {Uint(0), nil, ""}, - {EndObject, nil, ""}, - {Value(` { "0" : 0 , "0" : 0 } `), nil, ""}, - }, - wantOut: `{"0":0,"0":0}` + "\n" + `{"0":0,"0":0}` + "\n", -}, { - name: jsontest.Name("InvalidObject/DuplicateNames"), - calls: []encoderMethodCall{ - {BeginObject, nil, ""}, - {String("X"), nil, ""}, - {BeginObject, nil, ""}, - {EndObject, nil, ""}, - {String("X"), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"}, - {Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"}, - {String("Y"), nil, ""}, - {BeginObject, nil, ""}, - {EndObject, nil, ""}, - {String("X"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"}, - {Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"}, - {String("Y"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"}, - {Value(`"Y"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"}, - {EndObject, nil, ""}, - {Value(` { "X" : 0 , "Y" : 1 , "X" : 0 } `), E(ErrDuplicateName).withPos(`{"X":{},"Y":{}}`+"\n"+` { "X" : 0 , "Y" : 1 , `, "/X"), ""}, - }, - wantOut: `{"X":{},"Y":{}}` + "\n", -}, { - name: jsontest.Name("TruncatedArray/AfterStart"), - calls: []encoderMethodCall{ - {Value(`[`), E(io.ErrUnexpectedEOF).withPos(`[`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedArray/AfterValue"), - calls: []encoderMethodCall{ - {Value(`[0`), E(io.ErrUnexpectedEOF).withPos(`[0`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedArray/AfterComma"), - calls: []encoderMethodCall{ - {Value(`[0,`), E(io.ErrUnexpectedEOF).withPos(`[0,`, ""), ""}, - }, -}, { - name: jsontest.Name("TruncatedArray/MissingComma"), - calls: []encoderMethodCall{ - {Value(` [ "fizz" "buzz" ] `), newInvalidCharacterError("\"", "after array value (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, - }, -}, { - name: jsontest.Name("InvalidArray/MismatchingDelim"), - calls: []encoderMethodCall{ - {Value(` [ } `), newInvalidCharacterError("}", `at start of value`).withPos(` [ `, "/0"), ""}, - {BeginArray, nil, ""}, - {EndObject, E(errMismatchDelim).withPos(`[`, "/0"), ""}, - {Value(`}`), newInvalidCharacterError("}", "at start of value").withPos(`[`, "/0"), ""}, - {EndArray, nil, ""}, - }, - wantOut: "[]\n", -}, { - name: jsontest.Name("Format/Object/SpaceAfterColon"), - opts: []Options{SpaceAfterColon(true)}, - calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, - wantOut: "{\"fizz\": \"buzz\",\"wizz\": \"wuzz\"}\n", -}, { - name: jsontest.Name("Format/Object/SpaceAfterComma"), - opts: []Options{SpaceAfterComma(true)}, - calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, - wantOut: "{\"fizz\":\"buzz\", \"wizz\":\"wuzz\"}\n", -}, { - name: jsontest.Name("Format/Object/SpaceAfterColonAndComma"), - opts: []Options{SpaceAfterColon(true), SpaceAfterComma(true)}, - calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, - wantOut: "{\"fizz\": \"buzz\", \"wizz\": \"wuzz\"}\n", -}, { - name: jsontest.Name("Format/Object/NoSpaceAfterColon+SpaceAfterComma+Multiline"), - opts: []Options{SpaceAfterColon(false), SpaceAfterComma(true), Multiline(true)}, - calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, - wantOut: "{\n\t\"fizz\":\"buzz\", \n\t\"wizz\":\"wuzz\"\n}\n", -}, { - name: jsontest.Name("Format/Array/SpaceAfterComma"), - opts: []Options{SpaceAfterComma(true)}, - calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}}, - wantOut: "[\"fizz\", \"buzz\"]\n", -}, { - name: jsontest.Name("Format/Array/NoSpaceAfterComma+Multiline"), - opts: []Options{SpaceAfterComma(false), Multiline(true)}, - calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}}, - wantOut: "[\n\t\"fizz\",\n\t\"buzz\"\n]\n", -}, { - name: jsontest.Name("Format/ReorderWithWhitespace"), - opts: []Options{ - AllowDuplicateNames(true), - AllowInvalidUTF8(true), - ReorderRawObjects(true), - SpaceAfterComma(true), - SpaceAfterColon(false), - Multiline(true), - WithIndentPrefix(" "), - WithIndent("\t"), - PreserveRawStrings(true), - }, - calls: []encoderMethodCall{ - {BeginArray, nil, ""}, - {BeginArray, nil, ""}, - {Value(` { "fizz" : "buzz" , - "zip" : { - "x` + "\xfd" + `x" : 123 , "x` + "\xff" + `x" : 123, "x` + "\xfe" + `x" : 123 - }, - "zap" : { - "xxx" : 333, "xxx": 1, "xxx": 22 - }, - "alpha" : "bravo" } `), nil, ""}, - {EndArray, nil, ""}, - {EndArray, nil, ""}, - }, - wantOut: "[\n \t[\n \t\t{\n \t\t\t\"alpha\":\"bravo\", \n \t\t\t\"fizz\":\"buzz\", \n \t\t\t\"zap\":{\n \t\t\t\t\"xxx\":1, \n \t\t\t\t\"xxx\":22, \n \t\t\t\t\"xxx\":333\n \t\t\t}, \n \t\t\t\"zip\":{\n \t\t\t\t\"x\xfdx\":123, \n \t\t\t\t\"x\xfex\":123, \n \t\t\t\t\"x\xffx\":123\n \t\t\t}\n \t\t}\n \t]\n ]\n", -}, { - name: jsontest.Name("Format/CanonicalizeRawInts"), - opts: []Options{CanonicalizeRawInts(true), SpaceAfterComma(true)}, - calls: []encoderMethodCall{ - {Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""}, - }, - wantOut: "[0.100, 5.0, 1E6, -9223372036854776000, -10, -1, 0, 0, 1, 10, 9223372036854776000]\n", -}, { - name: jsontest.Name("Format/CanonicalizeRawFloats"), - opts: []Options{CanonicalizeRawFloats(true), SpaceAfterComma(true)}, - calls: []encoderMethodCall{ - {Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""}, - }, - wantOut: "[0.1, 5, 1000000, -9223372036854775808, -10, -1, 0, 0, 1, 10, 9223372036854775807]\n", -}, { - name: jsontest.Name("ErrorPosition"), - calls: []encoderMethodCall{ - {Value(` "a` + "\xff" + `0" `), E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""}, - {String(`a` + "\xff" + `0`), E(jsonwire.ErrInvalidUTF8).withPos(``, ""), ""}, - }, -}, { - name: jsontest.Name("ErrorPosition/0"), - calls: []encoderMethodCall{ - {Value(` [ "a` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, - {BeginArray, nil, ""}, - {Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`[ "a`, "/0"), ""}, - {String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`[`, "/0"), ""}, - }, - wantOut: `[`, -}, { - name: jsontest.Name("ErrorPosition/1"), - calls: []encoderMethodCall{ - {Value(` [ "a1" , "b` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, - {BeginArray, nil, ""}, - {String("a1"), nil, ""}, - {Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", "b`, "/1"), ""}, - {String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",`, "/1"), ""}, - }, - wantOut: `["a1"`, -}, { - name: jsontest.Name("ErrorPosition/0/0"), - calls: []encoderMethodCall{ - {Value(` [ [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, - {BeginArray, nil, ""}, - {Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a`, "/0/0"), ""}, - {BeginArray, nil, "/0"}, - {Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[[ "a`, "/0/0"), "/0"}, - {String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[[`, "/0/0"), "/0"}, - }, - wantOut: `[[`, -}, { - name: jsontest.Name("ErrorPosition/1/0"), - calls: []encoderMethodCall{ - {Value(` [ "a1" , [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""}, - {BeginArray, nil, ""}, - {String("a1"), nil, "/0"}, - {Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a`, "/1/0"), ""}, - {BeginArray, nil, "/1"}, - {Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[ "a`, "/1/0"), "/1"}, - {String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[`, "/1/0"), "/1"}, - }, - wantOut: `["a1",[`, -}, { - name: jsontest.Name("ErrorPosition/0/1"), - calls: []encoderMethodCall{ - {Value(` [ [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""}, - {BeginArray, nil, ""}, - {Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a2" , "b`, "/0/1"), ""}, - {BeginArray, nil, "/0"}, - {String("a2"), nil, "/0/0"}, - {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2", "b`, "/0/1"), "/0/0"}, - {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2",`, "/0/1"), "/0/0"}, - }, - wantOut: `[["a2"`, -}, { - name: jsontest.Name("ErrorPosition/1/1"), - calls: []encoderMethodCall{ - {Value(` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""}, - {BeginArray, nil, ""}, - {String("a1"), nil, "/0"}, - {Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a2" , "b`, "/1/1"), ""}, - {BeginArray, nil, "/1"}, - {String("a2"), nil, "/1/0"}, - {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2", "b`, "/1/1"), "/1/0"}, - {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2",`, "/1/1"), "/1/0"}, - }, - wantOut: `["a1",["a2"`, -}, { - name: jsontest.Name("ErrorPosition/a1-"), - calls: []encoderMethodCall{ - {Value(` { "a` + "\xff" + `1" : "b1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, - {BeginObject, nil, ""}, - {Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{ "a`, ""), ""}, - {String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{`, ""), ""}, - }, - wantOut: `{`, -}, { - name: jsontest.Name("ErrorPosition/a1"), - calls: []encoderMethodCall{ - {Value(` { "a1" : "b` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": "b`, "/a1"), ""}, - {String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":`, "/a1"), ""}, - }, - wantOut: `{"a1"`, -}, { - name: jsontest.Name("ErrorPosition/c1-"), - calls: []encoderMethodCall{ - {Value(` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {String("b1"), nil, "/a1"}, - {Value(` "c` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1": "c`, ""), "/a1"}, - {String(`c` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":`, ""), "/a1"}, - }, - wantOut: `{"a1":"b1"`, -}, { - name: jsontest.Name("ErrorPosition/c1"), - calls: []encoderMethodCall{ - {Value(` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {String("b1"), nil, "/a1"}, - {String("c1"), nil, "/c1"}, - {Value(` "d` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1": "d`, "/c1"), "/c1"}, - {String(`d` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1":`, "/c1"), "/c1"}, - }, - wantOut: `{"a1":"b1","c1"`, -}, { - name: jsontest.Name("ErrorPosition/a1/a2-"), - calls: []encoderMethodCall{ - {Value(` { "a1" : { "a` + "\xff" + `2" : "b2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {Value(` { "a` + "\xff" + `2" : "b2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a`, "/a1"), ""}, - {BeginObject, nil, "/a1"}, - {Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{ "a`, "/a1"), "/a1"}, - {String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{`, "/a1"), "/a1"}, - }, - wantOut: `{"a1":{`, -}, { - name: jsontest.Name("ErrorPosition/a1/a2"), - calls: []encoderMethodCall{ - {Value(` { "a1" : { "a2" : "b` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b`, "/a1/a2"), ""}, - {BeginObject, nil, "/a1"}, - {String("a2"), nil, "/a1/a2"}, - {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2": "b`, "/a1/a2"), "/a1/a2"}, - {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":`, "/a1/a2"), "/a1/a2"}, - }, - wantOut: `{"a1":{"a2"`, -}, { - name: jsontest.Name("ErrorPosition/a1/c2-"), - calls: []encoderMethodCall{ - {Value(` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {BeginObject, nil, "/a1"}, - {String("a2"), nil, "/a1/a2"}, - {String("b2"), nil, "/a1/a2"}, - {Value(` "c` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2", "c`, "/a1"), "/a1/a2"}, - {String(`c` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2",`, "/a1"), "/a1/a2"}, - }, - wantOut: `{"a1":{"a2":"b2"`, -}, { - name: jsontest.Name("ErrorPosition/a1/c2"), - calls: []encoderMethodCall{ - {Value(` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {Value(` { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, - {BeginObject, nil, ""}, - {String("a2"), nil, "/a1/a2"}, - {String("b2"), nil, "/a1/a2"}, - {String("c2"), nil, "/a1/c2"}, - {Value(` "d` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2": "d`, "/a1/c2"), "/a1/c2"}, - {String(`d` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2":`, "/a1/c2"), "/a1/c2"}, - }, - wantOut: `{"a1":{"a2":"b2","c2"`, -}, { - name: jsontest.Name("ErrorPosition/1/a2"), - calls: []encoderMethodCall{ - {Value(` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""}, - {BeginArray, nil, ""}, - {String("a1"), nil, "/0"}, - {Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", { "a2" : "b`, "/1/a2"), ""}, - {BeginObject, nil, "/1"}, - {String("a2"), nil, "/1/a2"}, - {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2": "b`, "/1/a2"), "/1/a2"}, - {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2":`, "/1/a2"), "/1/a2"}, - }, - wantOut: `["a1",{"a2"`, -}, { - name: jsontest.Name("ErrorPosition/c1/1"), - calls: []encoderMethodCall{ - {Value(` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""}, - {BeginObject, nil, ""}, - {String("a1"), nil, "/a1"}, - {String("b1"), nil, "/a1"}, - {String("c1"), nil, "/c1"}, - {Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1": [ "a2" , "b`, "/c1/1"), ""}, - {BeginArray, nil, "/c1"}, - {String("a2"), nil, "/c1/0"}, - {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2", "b`, "/c1/1"), "/c1/0"}, - {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2",`, "/c1/1"), "/c1/0"}, - }, - wantOut: `{"a1":"b1","c1":["a2"`, -}, { - name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"), - calls: []encoderMethodCall{ - {Value(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {BeginArray, nil, ""}, - {Value(` { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {BeginObject, nil, "/0"}, - {String("a1"), nil, "/0/a1"}, - {Value(` [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1": [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {BeginArray, nil, ""}, - {String("a2"), nil, "/0/a1/0"}, - {Value(` { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2", { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {BeginObject, nil, "/0/a1/1"}, - {String("a3"), nil, "/0/a1/1/a3"}, - {String("b3"), nil, "/0/a1/1/a3"}, - {String("c3"), nil, "/0/a1/1/c3"}, - {Value(` [ "a4" , "b` + "\xff" + `4" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3": [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, - {BeginArray, nil, "/0/a1/1/c3"}, - {String("a4"), nil, "/0/a1/1/c3/0"}, - {Value(` "b` + "\xff" + `4" `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4", "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, - {String(`b` + "\xff" + `4`), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4",`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, - }, - wantOut: `[{"a1":["a2",{"a3":"b3","c3":["a4"`, -}} - -// TestEncoderErrors test that Encoder errors occur when we expect and -// leaves the Encoder in a consistent state. -func TestEncoderErrors(t *testing.T) { - for _, td := range encoderErrorTestdata { - t.Run(path.Join(td.name.Name), func(t *testing.T) { - testEncoderErrors(t, td.name.Where, td.opts, td.calls, td.wantOut) - }) - } -} -func testEncoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, calls []encoderMethodCall, wantOut string) { - dst := new(bytes.Buffer) - enc := NewEncoder(dst, opts...) - for i, call := range calls { - var gotErr error - switch tokVal := call.in.(type) { - case Token: - gotErr = enc.WriteToken(tokVal) - case Value: - gotErr = enc.WriteValue(tokVal) - } - if !equalError(gotErr, call.wantErr) { - t.Fatalf("%s: %d: error mismatch:\ngot %v\nwant %v", where, i, gotErr, call.wantErr) - } - if call.wantPointer != "" { - gotPointer := enc.StackPointer() - if gotPointer != call.wantPointer { - t.Fatalf("%s: %d: Encoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer) - } - } - } - gotOut := dst.String() + string(enc.s.unflushedBuffer()) - if gotOut != wantOut { - t.Fatalf("%s: output mismatch:\ngot %q\nwant %q", where, gotOut, wantOut) - } - gotOffset := int(enc.OutputOffset()) - wantOffset := len(wantOut) - if gotOffset != wantOffset { - t.Fatalf("%s: Encoder.OutputOffset = %v, want %v", where, gotOffset, wantOffset) - } -} diff --git a/pkg/encoders/json/jsontext/example_test.go b/pkg/encoders/json/jsontext/example_test.go deleted file mode 100644 index 4bf6a7a..0000000 --- a/pkg/encoders/json/jsontext/example_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2023 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. - -//go:build goexperiment.jsonv2 - -package jsontext_test - -import ( - "bytes" - "fmt" - "io" - "log" - "strings" - - "encoding/json/jsontext" - "encoding/json/v2" -) - -// This example demonstrates the use of the [Encoder] and [Decoder] to -// parse and modify JSON without unmarshaling it into a concrete Go type. -func Example_stringReplace() { - // Example input with non-idiomatic use of "Golang" instead of "Go". - const input = `{ - "title": "Golang version 1 is released", - "author": "Andrew Gerrand", - "date": "2012-03-28", - "text": "Today marks a major milestone in the development of the Golang programming language.", - "otherArticles": [ - "Twelve Years of Golang", - "The Laws of Reflection", - "Learn Golang from your browser" - ] - }` - - // Using a Decoder and Encoder, we can parse through every token, - // check and modify the token if necessary, and - // write the token to the output. - var replacements []jsontext.Pointer - in := strings.NewReader(input) - dec := jsontext.NewDecoder(in) - out := new(bytes.Buffer) - enc := jsontext.NewEncoder(out, jsontext.Multiline(true)) // expand for readability - for { - // Read a token from the input. - tok, err := dec.ReadToken() - if err != nil { - if err == io.EOF { - break - } - log.Fatal(err) - } - - // Check whether the token contains the string "Golang" and - // replace each occurrence with "Go" instead. - if tok.Kind() == '"' && strings.Contains(tok.String(), "Golang") { - replacements = append(replacements, dec.StackPointer()) - tok = jsontext.String(strings.ReplaceAll(tok.String(), "Golang", "Go")) - } - - // Write the (possibly modified) token to the output. - if err := enc.WriteToken(tok); err != nil { - log.Fatal(err) - } - } - - // Print the list of replacements and the adjusted JSON output. - if len(replacements) > 0 { - fmt.Println(`Replaced "Golang" with "Go" in:`) - for _, where := range replacements { - fmt.Println("\t" + where) - } - fmt.Println() - } - fmt.Println("Result:", out.String()) - - // Output: - // Replaced "Golang" with "Go" in: - // /title - // /text - // /otherArticles/0 - // /otherArticles/2 - // - // Result: { - // "title": "Go version 1 is released", - // "author": "Andrew Gerrand", - // "date": "2012-03-28", - // "text": "Today marks a major milestone in the development of the Go programming language.", - // "otherArticles": [ - // "Twelve Years of Go", - // "The Laws of Reflection", - // "Learn Go from your browser" - // ] - // } -} - -// Directly embedding JSON within HTML requires special handling for safety. -// Escape certain runes to prevent JSON directly treated as HTML -// from being able to perform `, - } - - b, err := json.Marshal(&page, - // Escape certain runes within a JSON string so that - // JSON will be safe to directly embed inside HTML. - jsontext.EscapeForHTML(true), - jsontext.EscapeForJS(true), - jsontext.Multiline(true)) // expand for readability - if err != nil { - log.Fatal(err) - } - fmt.Println(string(b)) - - // Output: - // { - // "Title": "Example Embedded Javascript", - // "Body": "\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e" - // } -} diff --git a/pkg/encoders/json/jsontext/fuzz_test.go b/pkg/encoders/json/jsontext/fuzz_test.go deleted file mode 100644 index 60d16b9..0000000 --- a/pkg/encoders/json/jsontext/fuzz_test.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2023 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. - -//go:build goexperiment.jsonv2 - -package jsontext - -import ( - "bytes" - "errors" - "io" - "math/rand" - "slices" - "testing" - - "encoding/json/internal/jsontest" -) - -func FuzzCoder(f *testing.F) { - // Add a number of inputs to the corpus including valid and invalid data. - for _, td := range coderTestdata { - f.Add(int64(0), []byte(td.in)) - } - for _, td := range decoderErrorTestdata { - f.Add(int64(0), []byte(td.in)) - } - for _, td := range encoderErrorTestdata { - f.Add(int64(0), []byte(td.wantOut)) - } - for _, td := range jsontest.Data { - f.Add(int64(0), td.Data()) - } - - f.Fuzz(func(t *testing.T, seed int64, b []byte) { - var tokVals []tokOrVal - rn := rand.NewSource(seed) - - // Read a sequence of tokens or values. Skip the test for any errors - // since we expect this with randomly generated fuzz inputs. - src := bytes.NewReader(b) - dec := NewDecoder(src) - for { - if rn.Int63()%8 > 0 { - tok, err := dec.ReadToken() - if err != nil { - if err == io.EOF { - break - } - t.Skipf("Decoder.ReadToken error: %v", err) - } - tokVals = append(tokVals, tok.Clone()) - } else { - val, err := dec.ReadValue() - if err != nil { - expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']' - if expectError && errors.As(err, new(*SyntacticError)) { - continue - } - if err == io.EOF { - break - } - t.Skipf("Decoder.ReadValue error: %v", err) - } - tokVals = append(tokVals, append(zeroValue, val...)) - } - } - - // Write a sequence of tokens or values. Fail the test for any errors - // since the previous stage guarantees that the input is valid. - dst := new(bytes.Buffer) - enc := NewEncoder(dst) - for _, tokVal := range tokVals { - switch tokVal := tokVal.(type) { - case Token: - if err := enc.WriteToken(tokVal); err != nil { - t.Fatalf("Encoder.WriteToken error: %v", err) - } - case Value: - if err := enc.WriteValue(tokVal); err != nil { - t.Fatalf("Encoder.WriteValue error: %v", err) - } - } - } - - // Encoded output and original input must decode to the same thing. - var got, want []Token - for dec := NewDecoder(bytes.NewReader(b)); dec.PeekKind() > 0; { - tok, err := dec.ReadToken() - if err != nil { - t.Fatalf("Decoder.ReadToken error: %v", err) - } - got = append(got, tok.Clone()) - } - for dec := NewDecoder(dst); dec.PeekKind() > 0; { - tok, err := dec.ReadToken() - if err != nil { - t.Fatalf("Decoder.ReadToken error: %v", err) - } - want = append(want, tok.Clone()) - } - if !equalTokens(got, want) { - t.Fatalf("mismatching output:\ngot %v\nwant %v", got, want) - } - }) -} - -func FuzzResumableDecoder(f *testing.F) { - for _, td := range resumableDecoderTestdata { - f.Add(int64(0), []byte(td)) - } - - f.Fuzz(func(t *testing.T, seed int64, b []byte) { - rn := rand.NewSource(seed) - - // Regardless of how many bytes the underlying io.Reader produces, - // the provided tokens, values, and errors should always be identical. - t.Run("ReadToken", func(t *testing.T) { - decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn}) - decWant := NewDecoder(bytes.NewReader(b)) - gotTok, gotErr := decGot.ReadToken() - wantTok, wantErr := decWant.ReadToken() - if gotTok.String() != wantTok.String() || !equalError(gotErr, wantErr) { - t.Errorf("Decoder.ReadToken = (%v, %v), want (%v, %v)", gotTok, gotErr, wantTok, wantErr) - } - }) - t.Run("ReadValue", func(t *testing.T) { - decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn}) - decWant := NewDecoder(bytes.NewReader(b)) - gotVal, gotErr := decGot.ReadValue() - wantVal, wantErr := decWant.ReadValue() - if !slices.Equal(gotVal, wantVal) || !equalError(gotErr, wantErr) { - t.Errorf("Decoder.ReadValue = (%s, %v), want (%s, %v)", gotVal, gotErr, wantVal, wantErr) - } - }) - }) -} - -func FuzzValueFormat(f *testing.F) { - for _, td := range valueTestdata { - f.Add(int64(0), []byte(td.in)) - } - - // isValid reports whether b is valid according to the specified options. - isValid := func(b []byte, opts ...Options) bool { - d := NewDecoder(bytes.NewReader(b), opts...) - _, errVal := d.ReadValue() - _, errEOF := d.ReadToken() - return errVal == nil && errEOF == io.EOF - } - - // stripWhitespace removes all JSON whitespace characters from the input. - stripWhitespace := func(in []byte) (out []byte) { - out = make([]byte, 0, len(in)) - for _, c := range in { - switch c { - case ' ', '\n', '\r', '\t': - default: - out = append(out, c) - } - } - return out - } - - allOptions := []Options{ - AllowDuplicateNames(true), - AllowInvalidUTF8(true), - EscapeForHTML(true), - EscapeForJS(true), - PreserveRawStrings(true), - CanonicalizeRawInts(true), - CanonicalizeRawFloats(true), - ReorderRawObjects(true), - SpaceAfterColon(true), - SpaceAfterComma(true), - Multiline(true), - WithIndent("\t"), - WithIndentPrefix(" "), - } - - f.Fuzz(func(t *testing.T, seed int64, b []byte) { - validRFC7159 := isValid(b, AllowInvalidUTF8(true), AllowDuplicateNames(true)) - validRFC8259 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(true)) - validRFC7493 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(false)) - switch { - case !validRFC7159 && validRFC8259: - t.Errorf("invalid input per RFC 7159 implies invalid per RFC 8259") - case !validRFC8259 && validRFC7493: - t.Errorf("invalid input per RFC 8259 implies invalid per RFC 7493") - } - - gotValid := Value(b).IsValid() - wantValid := validRFC7493 - if gotValid != wantValid { - t.Errorf("Value.IsValid = %v, want %v", gotValid, wantValid) - } - - gotCompacted := Value(string(b)) - gotCompactOk := gotCompacted.Compact() == nil - wantCompactOk := validRFC7159 - if !bytes.Equal(stripWhitespace(gotCompacted), stripWhitespace(b)) { - t.Errorf("stripWhitespace(Value.Compact) = %s, want %s", stripWhitespace(gotCompacted), stripWhitespace(b)) - } - if gotCompactOk != wantCompactOk { - t.Errorf("Value.Compact success mismatch: got %v, want %v", gotCompactOk, wantCompactOk) - } - - gotIndented := Value(string(b)) - gotIndentOk := gotIndented.Indent() == nil - wantIndentOk := validRFC7159 - if !bytes.Equal(stripWhitespace(gotIndented), stripWhitespace(b)) { - t.Errorf("stripWhitespace(Value.Indent) = %s, want %s", stripWhitespace(gotIndented), stripWhitespace(b)) - } - if gotIndentOk != wantIndentOk { - t.Errorf("Value.Indent success mismatch: got %v, want %v", gotIndentOk, wantIndentOk) - } - - gotCanonicalized := Value(string(b)) - gotCanonicalizeOk := gotCanonicalized.Canonicalize() == nil - wantCanonicalizeOk := validRFC7493 - if gotCanonicalizeOk != wantCanonicalizeOk { - t.Errorf("Value.Canonicalize success mismatch: got %v, want %v", gotCanonicalizeOk, wantCanonicalizeOk) - } - - // Random options should not result in a panic. - var opts []Options - rn := rand.New(rand.NewSource(seed)) - for _, opt := range allOptions { - if rn.Intn(len(allOptions)/4) == 0 { - opts = append(opts, opt) - } - } - v := Value(b) - v.Format(opts...) // should not panic - }) -} diff --git a/pkg/encoders/json/jsontext/state_test.go b/pkg/encoders/json/jsontext/state_test.go deleted file mode 100644 index c227600..0000000 --- a/pkg/encoders/json/jsontext/state_test.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright 2020 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. - -//go:build goexperiment.jsonv2 - -package jsontext - -import ( - "fmt" - "slices" - "strings" - "testing" - "unicode/utf8" -) - -func TestPointer(t *testing.T) { - tests := []struct { - in Pointer - wantParent Pointer - wantLast string - wantTokens []string - wantValid bool - }{ - {"", "", "", nil, true}, - {"a", "", "a", []string{"a"}, false}, - {"~", "", "~", []string{"~"}, false}, - {"/a", "", "a", []string{"a"}, true}, - {"/foo/bar", "/foo", "bar", []string{"foo", "bar"}, true}, - {"///", "//", "", []string{"", "", ""}, true}, - {"/~0~1", "", "~/", []string{"~/"}, true}, - {"/\xde\xad\xbe\xef", "", "\xde\xad\xbe\xef", []string{"\xde\xad\xbe\xef"}, false}, - } - for _, tt := range tests { - if got := tt.in.Parent(); got != tt.wantParent { - t.Errorf("Pointer(%q).Parent = %q, want %q", tt.in, got, tt.wantParent) - } - if got := tt.in.LastToken(); got != tt.wantLast { - t.Errorf("Pointer(%q).Last = %q, want %q", tt.in, got, tt.wantLast) - } - if strings.HasPrefix(string(tt.in), "/") { - wantRoundtrip := tt.in - if !utf8.ValidString(string(wantRoundtrip)) { - // Replace bytes of invalid UTF-8 with Unicode replacement character. - wantRoundtrip = Pointer([]rune(wantRoundtrip)) - } - if got := tt.in.Parent().AppendToken(tt.in.LastToken()); got != wantRoundtrip { - t.Errorf("Pointer(%q).Parent().AppendToken(LastToken()) = %q, want %q", tt.in, got, tt.in) - } - in := tt.in - for { - if (in + "x").Contains(tt.in) { - t.Errorf("Pointer(%q).Contains(%q) = true, want false", in+"x", tt.in) - } - if !in.Contains(tt.in) { - t.Errorf("Pointer(%q).Contains(%q) = false, want true", in, tt.in) - } - if in == in.Parent() { - break - } - in = in.Parent() - } - } - if got := slices.Collect(tt.in.Tokens()); !slices.Equal(got, tt.wantTokens) { - t.Errorf("Pointer(%q).Tokens = %q, want %q", tt.in, got, tt.wantTokens) - } - if got := tt.in.IsValid(); got != tt.wantValid { - t.Errorf("Pointer(%q).IsValid = %v, want %v", tt.in, got, tt.wantValid) - } - } -} - -func TestStateMachine(t *testing.T) { - // To test a state machine, we pass an ordered sequence of operations and - // check whether the current state is as expected. - // The operation type is a union type of various possible operations, - // which either call mutating methods on the state machine or - // call accessor methods on state machine and verify the results. - type operation any - type ( - // stackLengths checks the results of stateEntry.length accessors. - stackLengths []int64 - - // appendTokens is sequence of token kinds to append where - // none of them are expected to fail. - // - // For example: `[nft]` is equivalent to the following sequence: - // - // pushArray() - // appendLiteral() - // appendString() - // appendNumber() - // popArray() - // - appendTokens string - - // appendToken is a single token kind to append with the expected error. - appendToken struct { - kind Kind - want error - } - - // needDelim checks the result of the needDelim accessor. - needDelim struct { - next Kind - want byte - } - ) - - // Each entry is a sequence of tokens to pass to the state machine. - tests := []struct { - label string - ops []operation - }{{ - "TopLevelValues", - []operation{ - stackLengths{0}, - needDelim{'n', 0}, - appendTokens(`nft`), - stackLengths{3}, - needDelim{'"', 0}, - appendTokens(`"0[]{}`), - stackLengths{7}, - }, - }, { - "ArrayValues", - []operation{ - stackLengths{0}, - needDelim{'[', 0}, - appendTokens(`[`), - stackLengths{1, 0}, - needDelim{'n', 0}, - appendTokens(`nft`), - stackLengths{1, 3}, - needDelim{'"', ','}, - appendTokens(`"0[]{}`), - stackLengths{1, 7}, - needDelim{']', 0}, - appendTokens(`]`), - stackLengths{1}, - }, - }, { - "ObjectValues", - []operation{ - stackLengths{0}, - needDelim{'{', 0}, - appendTokens(`{`), - stackLengths{1, 0}, - needDelim{'"', 0}, - appendTokens(`"`), - stackLengths{1, 1}, - needDelim{'n', ':'}, - appendTokens(`n`), - stackLengths{1, 2}, - needDelim{'"', ','}, - appendTokens(`"f"t`), - stackLengths{1, 6}, - appendTokens(`"""0"[]"{}`), - stackLengths{1, 14}, - needDelim{'}', 0}, - appendTokens(`}`), - stackLengths{1}, - }, - }, { - "ObjectCardinality", - []operation{ - appendTokens(`{`), - - // Appending any kind other than string for object name is an error. - appendToken{'n', ErrNonStringName}, - appendToken{'f', ErrNonStringName}, - appendToken{'t', ErrNonStringName}, - appendToken{'0', ErrNonStringName}, - appendToken{'{', ErrNonStringName}, - appendToken{'[', ErrNonStringName}, - appendTokens(`"`), - - // Appending '}' without first appending any value is an error. - appendToken{'}', errMissingValue}, - appendTokens(`"`), - - appendTokens(`}`), - }, - }, { - "MismatchingDelims", - []operation{ - appendToken{'}', errMismatchDelim}, // appending '}' without preceding '{' - appendTokens(`[[{`), - appendToken{']', errMismatchDelim}, // appending ']' that mismatches preceding '{' - appendTokens(`}]`), - appendToken{'}', errMismatchDelim}, // appending '}' that mismatches preceding '[' - appendTokens(`]`), - appendToken{']', errMismatchDelim}, // appending ']' without preceding '[' - }, - }} - - for _, tt := range tests { - t.Run(tt.label, func(t *testing.T) { - // Flatten appendTokens to sequence of appendToken entries. - var ops []operation - for _, op := range tt.ops { - if toks, ok := op.(appendTokens); ok { - for _, k := range []byte(toks) { - ops = append(ops, appendToken{Kind(k), nil}) - } - continue - } - ops = append(ops, op) - } - - // Append each token to the state machine and check the output. - var state stateMachine - state.reset() - var sequence []Kind - for _, op := range ops { - switch op := op.(type) { - case stackLengths: - var got []int64 - for i := range state.Depth() { - e := state.index(i) - got = append(got, e.Length()) - } - want := []int64(op) - if !slices.Equal(got, want) { - t.Fatalf("%s: stack lengths mismatch:\ngot %v\nwant %v", sequence, got, want) - } - case appendToken: - got := state.append(op.kind) - if !equalError(got, op.want) { - t.Fatalf("%s: append('%c') = %v, want %v", sequence, op.kind, got, op.want) - } - if got == nil { - sequence = append(sequence, op.kind) - } - case needDelim: - if got := state.needDelim(op.next); got != op.want { - t.Fatalf("%s: needDelim('%c') = '%c', want '%c'", sequence, op.next, got, op.want) - } - default: - panic(fmt.Sprintf("unknown operation: %T", op)) - } - } - }) - } -} - -// append is a thin wrapper over the other append, pop, or push methods -// based on the token kind. -func (s *stateMachine) append(k Kind) error { - switch k { - case 'n', 'f', 't': - return s.appendLiteral() - case '"': - return s.appendString() - case '0': - return s.appendNumber() - case '{': - return s.pushObject() - case '}': - return s.popObject() - case '[': - return s.pushArray() - case ']': - return s.popArray() - default: - panic(fmt.Sprintf("invalid token kind: '%c'", k)) - } -} - -func TestObjectNamespace(t *testing.T) { - type operation any - type ( - insert struct { - name string - wantInserted bool - } - removeLast struct{} - ) - - // Sequence of insert operations to perform (order matters). - ops := []operation{ - insert{`""`, true}, - removeLast{}, - insert{`""`, true}, - insert{`""`, false}, - - // Test insertion of the same name with different formatting. - insert{`"alpha"`, true}, - insert{`"ALPHA"`, true}, // case-sensitive matching - insert{`"alpha"`, false}, - insert{`"\u0061\u006c\u0070\u0068\u0061"`, false}, // unescapes to "alpha" - removeLast{}, // removes "ALPHA" - insert{`"alpha"`, false}, - removeLast{}, // removes "alpha" - insert{`"alpha"`, true}, - removeLast{}, - - // Bulk insert simple names. - insert{`"alpha"`, true}, - insert{`"bravo"`, true}, - insert{`"charlie"`, true}, - insert{`"delta"`, true}, - insert{`"echo"`, true}, - insert{`"foxtrot"`, true}, - insert{`"golf"`, true}, - insert{`"hotel"`, true}, - insert{`"india"`, true}, - insert{`"juliet"`, true}, - insert{`"kilo"`, true}, - insert{`"lima"`, true}, - insert{`"mike"`, true}, - insert{`"november"`, true}, - insert{`"oscar"`, true}, - insert{`"papa"`, true}, - insert{`"quebec"`, true}, - insert{`"romeo"`, true}, - insert{`"sierra"`, true}, - insert{`"tango"`, true}, - insert{`"uniform"`, true}, - insert{`"victor"`, true}, - insert{`"whiskey"`, true}, - insert{`"xray"`, true}, - insert{`"yankee"`, true}, - insert{`"zulu"`, true}, - - // Test insertion of invalid UTF-8. - insert{`"` + "\ufffd" + `"`, true}, - insert{`"` + "\ufffd" + `"`, false}, - insert{`"\ufffd"`, false}, // unescapes to Unicode replacement character - insert{`"\uFFFD"`, false}, // unescapes to Unicode replacement character - insert{`"` + "\xff" + `"`, false}, // mangles as Unicode replacement character - removeLast{}, - insert{`"` + "\ufffd" + `"`, true}, - - // Test insertion of unicode characters. - insert{`"☺☻☹"`, true}, - insert{`"☺☻☹"`, false}, - removeLast{}, - insert{`"☺☻☹"`, true}, - } - - // Execute the sequence of operations twice: - // 1) on a fresh namespace and 2) on a namespace that has been reset. - var ns objectNamespace - wantNames := []string{} - for _, reset := range []bool{false, true} { - if reset { - ns.reset() - wantNames = nil - } - - // Execute the operations and ensure the state is consistent. - for i, op := range ops { - switch op := op.(type) { - case insert: - gotInserted := ns.insertQuoted([]byte(op.name), false) - if gotInserted != op.wantInserted { - t.Fatalf("%d: objectNamespace{%v}.insert(%v) = %v, want %v", i, strings.Join(wantNames, " "), op.name, gotInserted, op.wantInserted) - } - if gotInserted { - b, _ := AppendUnquote(nil, []byte(op.name)) - wantNames = append(wantNames, string(b)) - } - case removeLast: - ns.removeLast() - wantNames = wantNames[:len(wantNames)-1] - default: - panic(fmt.Sprintf("unknown operation: %T", op)) - } - - // Check that the namespace is consistent. - gotNames := []string{} - for i := range ns.length() { - gotNames = append(gotNames, string(ns.getUnquoted(i))) - } - if !slices.Equal(gotNames, wantNames) { - t.Fatalf("%d: objectNamespace = {%v}, want {%v}", i, strings.Join(gotNames, " "), strings.Join(wantNames, " ")) - } - } - - // Verify that we have not switched to using a Go map. - if ns.mapNames != nil { - t.Errorf("objectNamespace.mapNames = non-nil, want nil") - } - - // Insert a large number of names. - for i := range 64 { - ns.InsertUnquoted([]byte(fmt.Sprintf(`name%d`, i))) - } - - // Verify that we did switch to using a Go map. - if ns.mapNames == nil { - t.Errorf("objectNamespace.mapNames = nil, want non-nil") - } - } -} diff --git a/pkg/encoders/json/jsontext/token_test.go b/pkg/encoders/json/jsontext/token_test.go deleted file mode 100644 index ebe324e..0000000 --- a/pkg/encoders/json/jsontext/token_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2020 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. - -//go:build goexperiment.jsonv2 - -package jsontext - -import ( - "math" - "reflect" - "testing" -) - -func TestTokenStringAllocations(t *testing.T) { - if testing.CoverMode() != "" { - t.Skip("coverage mode breaks the compiler optimization this depends on") - } - - tok := rawToken(`"hello"`) - var m map[string]bool - got := int(testing.AllocsPerRun(10, func() { - // This function uses tok.String() is a non-escaping manner - // (i.e., looking it up in a Go map). It should not allocate. - if m[tok.String()] { - panic("never executed") - } - })) - if got > 0 { - t.Errorf("Token.String allocated %d times, want 0", got) - } -} - -func TestTokenAccessors(t *testing.T) { - type token struct { - Bool bool - String string - Float float64 - Int int64 - Uint uint64 - Kind Kind - } - - tests := []struct { - in Token - want token - }{ - {Token{}, token{String: ""}}, - {Null, token{String: "null", Kind: 'n'}}, - {False, token{Bool: false, String: "false", Kind: 'f'}}, - {True, token{Bool: true, String: "true", Kind: 't'}}, - {Bool(false), token{Bool: false, String: "false", Kind: 'f'}}, - {Bool(true), token{Bool: true, String: "true", Kind: 't'}}, - {BeginObject, token{String: "{", Kind: '{'}}, - {EndObject, token{String: "}", Kind: '}'}}, - {BeginArray, token{String: "[", Kind: '['}}, - {EndArray, token{String: "]", Kind: ']'}}, - {String(""), token{String: "", Kind: '"'}}, - {String("hello, world!"), token{String: "hello, world!", Kind: '"'}}, - {rawToken(`"hello, world!"`), token{String: "hello, world!", Kind: '"'}}, - {Float(0), token{String: "0", Float: 0, Int: 0, Uint: 0, Kind: '0'}}, - {Float(math.Copysign(0, -1)), token{String: "-0", Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}}, - {Float(math.NaN()), token{String: "NaN", Float: math.NaN(), Int: 0, Uint: 0, Kind: '"'}}, - {Float(math.Inf(+1)), token{String: "Infinity", Float: math.Inf(+1), Kind: '"'}}, - {Float(math.Inf(-1)), token{String: "-Infinity", Float: math.Inf(-1), Kind: '"'}}, - {Int(minInt64), token{String: "-9223372036854775808", Float: minInt64, Int: minInt64, Uint: minUint64, Kind: '0'}}, - {Int(minInt64 + 1), token{String: "-9223372036854775807", Float: minInt64 + 1, Int: minInt64 + 1, Uint: minUint64, Kind: '0'}}, - {Int(-1), token{String: "-1", Float: -1, Int: -1, Uint: minUint64, Kind: '0'}}, - {Int(0), token{String: "0", Float: 0, Int: 0, Uint: 0, Kind: '0'}}, - {Int(+1), token{String: "1", Float: +1, Int: +1, Uint: +1, Kind: '0'}}, - {Int(maxInt64 - 1), token{String: "9223372036854775806", Float: maxInt64 - 1, Int: maxInt64 - 1, Uint: maxInt64 - 1, Kind: '0'}}, - {Int(maxInt64), token{String: "9223372036854775807", Float: maxInt64, Int: maxInt64, Uint: maxInt64, Kind: '0'}}, - {Uint(minUint64), token{String: "0", Kind: '0'}}, - {Uint(minUint64 + 1), token{String: "1", Float: minUint64 + 1, Int: minUint64 + 1, Uint: minUint64 + 1, Kind: '0'}}, - {Uint(maxUint64 - 1), token{String: "18446744073709551614", Float: maxUint64 - 1, Int: maxInt64, Uint: maxUint64 - 1, Kind: '0'}}, - {Uint(maxUint64), token{String: "18446744073709551615", Float: maxUint64, Int: maxInt64, Uint: maxUint64, Kind: '0'}}, - {rawToken(`-0`), token{String: "-0", Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}}, - {rawToken(`1e1000`), token{String: "1e1000", Float: math.MaxFloat64, Int: maxInt64, Uint: maxUint64, Kind: '0'}}, - {rawToken(`-1e1000`), token{String: "-1e1000", Float: -math.MaxFloat64, Int: minInt64, Uint: minUint64, Kind: '0'}}, - {rawToken(`0.1`), token{String: "0.1", Float: 0.1, Int: 0, Uint: 0, Kind: '0'}}, - {rawToken(`0.5`), token{String: "0.5", Float: 0.5, Int: 0, Uint: 0, Kind: '0'}}, - {rawToken(`0.9`), token{String: "0.9", Float: 0.9, Int: 0, Uint: 0, Kind: '0'}}, - {rawToken(`1.1`), token{String: "1.1", Float: 1.1, Int: 1, Uint: 1, Kind: '0'}}, - {rawToken(`-0.1`), token{String: "-0.1", Float: -0.1, Int: 0, Uint: 0, Kind: '0'}}, - {rawToken(`-0.5`), token{String: "-0.5", Float: -0.5, Int: 0, Uint: 0, Kind: '0'}}, - {rawToken(`-0.9`), token{String: "-0.9", Float: -0.9, Int: 0, Uint: 0, Kind: '0'}}, - {rawToken(`-1.1`), token{String: "-1.1", Float: -1.1, Int: -1, Uint: 0, Kind: '0'}}, - {rawToken(`99999999999999999999`), token{String: "99999999999999999999", Float: 1e20 - 1, Int: maxInt64, Uint: maxUint64, Kind: '0'}}, - {rawToken(`-99999999999999999999`), token{String: "-99999999999999999999", Float: -1e20 - 1, Int: minInt64, Uint: minUint64, Kind: '0'}}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - got := token{ - Bool: func() bool { - defer func() { recover() }() - return tt.in.Bool() - }(), - String: tt.in.String(), - Float: func() float64 { - defer func() { recover() }() - return tt.in.Float() - }(), - Int: func() int64 { - defer func() { recover() }() - return tt.in.Int() - }(), - Uint: func() uint64 { - defer func() { recover() }() - return tt.in.Uint() - }(), - Kind: tt.in.Kind(), - } - - if got.Bool != tt.want.Bool { - t.Errorf("Token(%s).Bool() = %v, want %v", tt.in, got.Bool, tt.want.Bool) - } - if got.String != tt.want.String { - t.Errorf("Token(%s).String() = %v, want %v", tt.in, got.String, tt.want.String) - } - if math.Float64bits(got.Float) != math.Float64bits(tt.want.Float) { - t.Errorf("Token(%s).Float() = %v, want %v", tt.in, got.Float, tt.want.Float) - } - if got.Int != tt.want.Int { - t.Errorf("Token(%s).Int() = %v, want %v", tt.in, got.Int, tt.want.Int) - } - if got.Uint != tt.want.Uint { - t.Errorf("Token(%s).Uint() = %v, want %v", tt.in, got.Uint, tt.want.Uint) - } - if got.Kind != tt.want.Kind { - t.Errorf("Token(%s).Kind() = %v, want %v", tt.in, got.Kind, tt.want.Kind) - } - }) - } -} - -func TestTokenClone(t *testing.T) { - tests := []struct { - in Token - wantExactRaw bool - }{ - {Token{}, true}, - {Null, true}, - {False, true}, - {True, true}, - {BeginObject, true}, - {EndObject, true}, - {BeginArray, true}, - {EndArray, true}, - {String("hello, world!"), true}, - {rawToken(`"hello, world!"`), false}, - {Float(3.14159), true}, - {rawToken(`3.14159`), false}, - } - - for _, tt := range tests { - t.Run("", func(t *testing.T) { - got := tt.in.Clone() - if !reflect.DeepEqual(got, tt.in) { - t.Errorf("Token(%s) == Token(%s).Clone() = false, want true", tt.in, tt.in) - } - gotExactRaw := got.raw == tt.in.raw - if gotExactRaw != tt.wantExactRaw { - t.Errorf("Token(%s).raw == Token(%s).Clone().raw = %v, want %v", tt.in, tt.in, gotExactRaw, tt.wantExactRaw) - } - }) - } -} diff --git a/pkg/encoders/json/jsontext/value_test.go b/pkg/encoders/json/jsontext/value_test.go deleted file mode 100644 index 184a27d..0000000 --- a/pkg/encoders/json/jsontext/value_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2020 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. - -//go:build goexperiment.jsonv2 - -package jsontext - -import ( - "io" - "strings" - "testing" - - "encoding/json/internal/jsontest" - "encoding/json/internal/jsonwire" -) - -type valueTestdataEntry struct { - name jsontest.CaseName - in string - wantValid bool - wantCompacted string - wantCompactErr error // implies wantCompacted is in - wantIndented string // wantCompacted if empty; uses "\t" for indent prefix and " " for indent - wantIndentErr error // implies wantCompacted is in - wantCanonicalized string // wantCompacted if empty - wantCanonicalizeErr error // implies wantCompacted is in -} - -var valueTestdata = append(func() (out []valueTestdataEntry) { - // Initialize valueTestdata from coderTestdata. - for _, td := range coderTestdata { - // NOTE: The Compact method preserves the raw formatting of strings, - // while the Encoder (by default) does not. - if td.name.Name == "ComplicatedString" { - td.outCompacted = strings.TrimSpace(td.in) - } - out = append(out, valueTestdataEntry{ - name: td.name, - in: td.in, - wantValid: true, - wantCompacted: td.outCompacted, - wantIndented: td.outIndented, - wantCanonicalized: td.outCanonicalized, - }) - } - return out -}(), []valueTestdataEntry{{ - name: jsontest.Name("RFC8785/Primitives"), - in: `{ - "numbers": [333333333.33333329, 1E30, 4.50, - 2e-3, 0.000000000000000000000000001, -0], - "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/", - "literals": [null, true, false] - }`, - wantValid: true, - wantCompacted: `{"numbers":[333333333.33333329,1E30,4.50,2e-3,0.000000000000000000000000001,-0],"string":"\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/","literals":[null,true,false]}`, - wantIndented: `{ - "numbers": [ - 333333333.33333329, - 1E30, - 4.50, - 2e-3, - 0.000000000000000000000000001, - -0 - ], - "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/", - "literals": [ - null, - true, - false - ] - }`, - wantCanonicalized: `{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27,0],"string":"€$\u000f\nA'B\"\\\\\"/"}`, -}, { - name: jsontest.Name("RFC8785/ObjectOrdering"), - in: `{ - "\u20ac": "Euro Sign", - "\r": "Carriage Return", - "\ufb33": "Hebrew Letter Dalet With Dagesh", - "1": "One", - "\ud83d\ude00": "Emoji: Grinning Face", - "\u0080": "Control", - "\u00f6": "Latin Small Letter O With Diaeresis" - }`, - wantValid: true, - wantCompacted: `{"\u20ac":"Euro Sign","\r":"Carriage Return","\ufb33":"Hebrew Letter Dalet With Dagesh","1":"One","\ud83d\ude00":"Emoji: Grinning Face","\u0080":"Control","\u00f6":"Latin Small Letter O With Diaeresis"}`, - wantIndented: `{ - "\u20ac": "Euro Sign", - "\r": "Carriage Return", - "\ufb33": "Hebrew Letter Dalet With Dagesh", - "1": "One", - "\ud83d\ude00": "Emoji: Grinning Face", - "\u0080": "Control", - "\u00f6": "Latin Small Letter O With Diaeresis" - }`, - wantCanonicalized: `{"\r":"Carriage Return","1":"One","€":"Control","ö":"Latin Small Letter O With Diaeresis","€":"Euro Sign","😀":"Emoji: Grinning Face","דּ":"Hebrew Letter Dalet With Dagesh"}`, -}, { - name: jsontest.Name("LargeIntegers"), - in: ` [ -9223372036854775808 , 9223372036854775807 ] `, - wantValid: true, - wantCompacted: `[-9223372036854775808,9223372036854775807]`, - wantIndented: `[ - -9223372036854775808, - 9223372036854775807 - ]`, - wantCanonicalized: `[-9223372036854776000,9223372036854776000]`, // NOTE: Loss of precision due to numbers being treated as floats. -}, { - name: jsontest.Name("InvalidUTF8"), - in: ` "living` + "\xde\xad\xbe\xef" + `\ufffd�" `, - wantValid: false, // uses RFC 7493 as the definition; which validates UTF-8 - wantCompacted: `"living` + "\xde\xad\xbe\xef" + `\ufffd�"`, - wantCanonicalizeErr: E(jsonwire.ErrInvalidUTF8).withPos(` "living`+"\xde\xad", ""), -}, { - name: jsontest.Name("InvalidUTF8/SurrogateHalf"), - in: `"\ud800"`, - wantValid: false, // uses RFC 7493 as the definition; which validates UTF-8 - wantCompacted: `"\ud800"`, - wantCanonicalizeErr: newInvalidEscapeSequenceError(`\ud800"`).withPos(`"`, ""), -}, { - name: jsontest.Name("UppercaseEscaped"), - in: `"\u000B"`, - wantValid: true, - wantCompacted: `"\u000B"`, - wantCanonicalized: `"\u000b"`, -}, { - name: jsontest.Name("DuplicateNames"), - in: ` { "0" : 0 , "1" : 1 , "0" : 0 }`, - wantValid: false, // uses RFC 7493 as the definition; which does check for object uniqueness - wantCompacted: `{"0":0,"1":1,"0":0}`, - wantIndented: `{ - "0": 0, - "1": 1, - "0": 0 - }`, - wantCanonicalizeErr: E(ErrDuplicateName).withPos(` { "0" : 0 , "1" : 1 , `, "/0"), -}, { - name: jsontest.Name("Whitespace"), - in: " \n\r\t", - wantValid: false, - wantCompacted: " \n\r\t", - wantCompactErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""), - wantIndentErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""), - wantCanonicalizeErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""), -}}...) - -func TestValueMethods(t *testing.T) { - for _, td := range valueTestdata { - t.Run(td.name.Name, func(t *testing.T) { - if td.wantIndented == "" { - td.wantIndented = td.wantCompacted - } - if td.wantCanonicalized == "" { - td.wantCanonicalized = td.wantCompacted - } - if td.wantCompactErr != nil { - td.wantCompacted = td.in - } - if td.wantIndentErr != nil { - td.wantIndented = td.in - } - if td.wantCanonicalizeErr != nil { - td.wantCanonicalized = td.in - } - - v := Value(td.in) - gotValid := v.IsValid() - if gotValid != td.wantValid { - t.Errorf("%s: Value.IsValid = %v, want %v", td.name.Where, gotValid, td.wantValid) - } - - gotCompacted := Value(td.in) - gotCompactErr := gotCompacted.Compact() - if string(gotCompacted) != td.wantCompacted { - t.Errorf("%s: Value.Compact = %s, want %s", td.name.Where, gotCompacted, td.wantCompacted) - } - if !equalError(gotCompactErr, td.wantCompactErr) { - t.Errorf("%s: Value.Compact error mismatch:\ngot %v\nwant %v", td.name.Where, gotCompactErr, td.wantCompactErr) - } - - gotIndented := Value(td.in) - gotIndentErr := gotIndented.Indent(WithIndentPrefix("\t"), WithIndent(" ")) - if string(gotIndented) != td.wantIndented { - t.Errorf("%s: Value.Indent = %s, want %s", td.name.Where, gotIndented, td.wantIndented) - } - if !equalError(gotIndentErr, td.wantIndentErr) { - t.Errorf("%s: Value.Indent error mismatch:\ngot %v\nwant %v", td.name.Where, gotIndentErr, td.wantIndentErr) - } - - gotCanonicalized := Value(td.in) - gotCanonicalizeErr := gotCanonicalized.Canonicalize() - if string(gotCanonicalized) != td.wantCanonicalized { - t.Errorf("%s: Value.Canonicalize = %s, want %s", td.name.Where, gotCanonicalized, td.wantCanonicalized) - } - if !equalError(gotCanonicalizeErr, td.wantCanonicalizeErr) { - t.Errorf("%s: Value.Canonicalize error mismatch:\ngot %v\nwant %v", td.name.Where, gotCanonicalizeErr, td.wantCanonicalizeErr) - } - }) - } -} diff --git a/pkg/encoders/json/number_test.go b/pkg/encoders/json/number_test.go deleted file mode 100644 index 69eccaa..0000000 --- a/pkg/encoders/json/number_test.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2011 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. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "regexp" - "testing" -) - -func TestNumberIsValid(t *testing.T) { - // From: https://stackoverflow.com/a/13340826 - var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) - - validTests := []string{ - "0", - "-0", - "1", - "-1", - "0.1", - "-0.1", - "1234", - "-1234", - "12.34", - "-12.34", - "12E0", - "12E1", - "12e34", - "12E-0", - "12e+1", - "12e-34", - "-12E0", - "-12E1", - "-12e34", - "-12E-0", - "-12e+1", - "-12e-34", - "1.2E0", - "1.2E1", - "1.2e34", - "1.2E-0", - "1.2e+1", - "1.2e-34", - "-1.2E0", - "-1.2E1", - "-1.2e34", - "-1.2E-0", - "-1.2e+1", - "-1.2e-34", - "0E0", - "0E1", - "0e34", - "0E-0", - "0e+1", - "0e-34", - "-0E0", - "-0E1", - "-0e34", - "-0E-0", - "-0e+1", - "-0e-34", - } - - for _, test := range validTests { - if !isValidNumber(test) { - t.Errorf("%s should be valid", test) - } - - var f float64 - if err := Unmarshal([]byte(test), &f); err != nil { - t.Errorf("%s should be valid but Unmarshal failed: %v", test, err) - } - - if !jsonNumberRegexp.MatchString(test) { - t.Errorf("%s should be valid but regexp does not match", test) - } - } - - invalidTests := []string{ - "", - "invalid", - "1.0.1", - "1..1", - "-1-2", - "012a42", - "01.2", - "012", - "12E12.12", - "1e2e3", - "1e+-2", - "1e--23", - "1e", - "e1", - "1e+", - "1ea", - "1a", - "1.a", - "1.", - "01", - "1.e1", - } - - for _, test := range invalidTests { - if isValidNumber(test) { - t.Errorf("%s should be invalid", test) - } - - var f float64 - if err := Unmarshal([]byte(test), &f); err == nil { - t.Errorf("%s should be invalid but unmarshal wrote %v", test, f) - } - - if jsonNumberRegexp.MatchString(test) { - t.Errorf("%s should be invalid but matches regexp", test) - } - } -} diff --git a/pkg/encoders/json/scanner_test.go b/pkg/encoders/json/scanner_test.go deleted file mode 100644 index fb64463..0000000 --- a/pkg/encoders/json/scanner_test.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2010 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. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "bytes" - "math" - "math/rand" - "reflect" - "strings" - "testing" -) - -func indentNewlines(s string) string { - return strings.Join(strings.Split(s, "\n"), "\n\t") -} - -func stripWhitespace(s string) string { - return strings.Map(func(r rune) rune { - if r == ' ' || r == '\n' || r == '\r' || r == '\t' { - return -1 - } - return r - }, s) -} - -func TestValid(t *testing.T) { - tests := []struct { - CaseName - data string - ok bool - }{ - {Name(""), `foo`, false}, - {Name(""), `}{`, false}, - {Name(""), `{]`, false}, - {Name(""), `{}`, true}, - {Name(""), `{"foo":"bar"}`, true}, - {Name(""), `{"foo":"bar","bar":{"baz":["qux"]}}`, true}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - if ok := Valid([]byte(tt.data)); ok != tt.ok { - t.Errorf("%s: Valid(`%s`) = %v, want %v", tt.Where, tt.data, ok, tt.ok) - } - }) - } -} - -func TestCompactAndIndent(t *testing.T) { - tests := []struct { - CaseName - compact string - indent string - }{ - {Name(""), `1`, `1`}, - {Name(""), `{}`, `{}`}, - {Name(""), `[]`, `[]`}, - {Name(""), `{"":2}`, "{\n\t\"\": 2\n}"}, - {Name(""), `[3]`, "[\n\t3\n]"}, - {Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, - {Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"}, - {Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[ - true, - false, - null, - "x", - 1, - 1.5, - 0, - -5e+2 -]`}, - {Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 - } - var buf bytes.Buffer - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - buf.Reset() - if err := Compact(&buf, []byte(tt.compact)); err != nil { - t.Errorf("%s: Compact error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.compact { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) - } - - buf.Reset() - if err := Compact(&buf, []byte(tt.indent)); err != nil { - t.Errorf("%s: Compact error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.compact { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) - } - - buf.Reset() - if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { - t.Errorf("%s: Indent error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.indent { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) - } - - buf.Reset() - if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { - t.Errorf("%s: Indent error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.indent { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) - } - }) - } -} - -func TestCompactSeparators(t *testing.T) { - // U+2028 and U+2029 should be escaped inside strings. - // They should not appear outside strings. - tests := []struct { - CaseName - in, compact string - }{ - {Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"}, - {Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - var buf bytes.Buffer - if err := Compact(&buf, []byte(tt.in)); err != nil { - t.Errorf("%s: Compact error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.compact { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) - } - }) - } -} - -// Tests of a large random structure. - -func TestCompactBig(t *testing.T) { - initBig() - var buf bytes.Buffer - if err := Compact(&buf, jsonBig); err != nil { - t.Fatalf("Compact error: %v", err) - } - b := buf.Bytes() - if !bytes.Equal(b, jsonBig) { - t.Error("Compact:") - diff(t, b, jsonBig) - return - } -} - -func TestIndentBig(t *testing.T) { - t.Parallel() - initBig() - var buf bytes.Buffer - if err := Indent(&buf, jsonBig, "", "\t"); err != nil { - t.Fatalf("Indent error: %v", err) - } - b := buf.Bytes() - if len(b) == len(jsonBig) { - // jsonBig is compact (no unnecessary spaces); - // indenting should make it bigger - t.Fatalf("Indent did not expand the input") - } - - // should be idempotent - var buf1 bytes.Buffer - if err := Indent(&buf1, b, "", "\t"); err != nil { - t.Fatalf("Indent error: %v", err) - } - b1 := buf1.Bytes() - if !bytes.Equal(b1, b) { - t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):") - diff(t, b1, b) - return - } - - // should get back to original - buf1.Reset() - if err := Compact(&buf1, b); err != nil { - t.Fatalf("Compact error: %v", err) - } - b1 = buf1.Bytes() - if !bytes.Equal(b1, jsonBig) { - t.Error("Compact(Indent(jsonBig)) != jsonBig:") - diff(t, b1, jsonBig) - return - } -} - -func TestIndentErrors(t *testing.T) { - tests := []struct { - CaseName - in string - err error - }{ - {Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, - {Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - slice := make([]uint8, 0) - buf := bytes.NewBuffer(slice) - if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { - if !reflect.DeepEqual(err, tt.err) { - t.Fatalf("%s: Indent error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) - } - } - }) - } -} - -func diff(t *testing.T, a, b []byte) { - t.Helper() - for i := 0; ; i++ { - if i >= len(a) || i >= len(b) || a[i] != b[i] { - j := i - 10 - if j < 0 { - j = 0 - } - t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) - return - } - } -} - -func trim(b []byte) []byte { - return b[:min(len(b), 20)] -} - -// Generate a random JSON object. - -var jsonBig []byte - -func initBig() { - n := 10000 - if testing.Short() { - n = 100 - } - b, err := Marshal(genValue(n)) - if err != nil { - panic(err) - } - jsonBig = b -} - -func genValue(n int) any { - if n > 1 { - switch rand.Intn(2) { - case 0: - return genArray(n) - case 1: - return genMap(n) - } - } - switch rand.Intn(3) { - case 0: - return rand.Intn(2) == 0 - case 1: - return rand.NormFloat64() - case 2: - return genString(30) - } - panic("unreachable") -} - -func genString(stddev float64) string { - n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) - c := make([]rune, n) - for i := range c { - f := math.Abs(rand.NormFloat64()*64 + 32) - if f > 0x10ffff { - f = 0x10ffff - } - c[i] = rune(f) - } - return string(c) -} - -func genArray(n int) []any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if f < 1 { - f = 1 - } - 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]any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if n > 0 && f == 0 { - f = 1 - } - x := make(map[string]any) - for i := 0; i < f; i++ { - x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) - } - return x -} diff --git a/pkg/encoders/json/stream_test.go b/pkg/encoders/json/stream_test.go deleted file mode 100644 index 478ee18..0000000 --- a/pkg/encoders/json/stream_test.go +++ /dev/null @@ -1,524 +0,0 @@ -// Copyright 2010 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. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "bytes" - "fmt" - "io" - "log" - "net" - "net/http" - "net/http/httptest" - "path" - "reflect" - "runtime" - "runtime/debug" - "strings" - "testing" -) - -// TODO(https://go.dev/issue/52751): Replace with native testing support. - -// CaseName is a case name annotated with a file and line. -type CaseName struct { - Name string - Where CasePos -} - -// Name annotates a case name with the file and line of the caller. -func Name(s string) (c CaseName) { - c.Name = s - runtime.Callers(2, c.Where.pc[:]) - return c -} - -// CasePos represents a file and line number. -type CasePos struct{ pc [1]uintptr } - -func (pos CasePos) String() string { - frames := runtime.CallersFrames(pos.pc[:]) - frame, _ := frames.Next() - return fmt.Sprintf("%s:%d", path.Base(frame.File), frame.Line) -} - -// Test values for the stream test. -// One of each JSON kind. -var streamTest = []any{ - 0.1, - "hello", - nil, - true, - false, - []any{"a", "b", "c"}, - map[string]any{"K": "Kelvin", "ß": "long s"}, - 3.14, // another value to make sure something can follow map -} - -var streamEncoded = `0.1 -"hello" -null -true -false -["a","b","c"] -{"ß":"long s","K":"Kelvin"} -3.14 -` - -func TestEncoder(t *testing.T) { - for i := 0; i <= len(streamTest); i++ { - var buf strings.Builder - enc := NewEncoder(&buf) - // Check that enc.SetIndent("", "") turns off indentation. - enc.SetIndent(">", ".") - enc.SetIndent("", "") - for j, v := range streamTest[0:i] { - if err := enc.Encode(v); err != nil { - t.Fatalf("#%d.%d Encode error: %v", i, j, err) - } - } - if got, want := buf.String(), nlines(streamEncoded, i); got != want { - t.Errorf("encoding %d items: mismatch:", i) - diff(t, []byte(got), []byte(want)) - break - } - } -} - -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) error: got nil, want non-nil") - } - - type Data struct { - A string - I int - } - want := Data{A: "a", I: 1} - if err := enc.Encode(want); err != nil { - t.Errorf("Marshal error: %v", err) - } - - var got Data - if err := Unmarshal(buf.Bytes(), &got); err != nil { - t.Errorf("Unmarshal error: %v", err) - } - if got != want { - t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %v\n\twant: %v", got, want) - } -} - -var streamEncodedIndent = `0.1 -"hello" -null -true -false -[ ->."a", ->."b", ->."c" ->] -{ ->."ß": "long s", ->."K": "Kelvin" ->} -3.14 -` - -func TestEncoderIndent(t *testing.T) { - var buf strings.Builder - enc := NewEncoder(&buf) - enc.SetIndent(">", ".") - for _, v := range streamTest { - enc.Encode(v) - } - if got, want := buf.String(), streamEncodedIndent; got != want { - t.Errorf("Encode mismatch:\ngot:\n%s\n\nwant:\n%s", got, want) - diff(t, []byte(got), []byte(want)) - } -} - -type strMarshaler string - -func (s strMarshaler) MarshalJSON() ([]byte, error) { - return []byte(s), nil -} - -type strPtrMarshaler string - -func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) { - return []byte(*s), nil -} - -func TestEncoderSetEscapeHTML(t *testing.T) { - var c C - var ct CText - var tagStruct struct { - Valid int `json:"<>&#! "` - Invalid int `json:"\\"` - } - - // This case is particularly interesting, as we force the encoder to - // take the address of the Ptr field to use its MarshalJSON method. This - // is why the '&' is important. - marshalerStruct := &struct { - NonPtr strMarshaler - Ptr strPtrMarshaler - }{`""`, `""`} - - // https://golang.org/issue/34154 - stringOption := struct { - Bar string `json:"bar,string"` - }{`foobar`} - - tests := []struct { - CaseName - v any - wantEscape string - want string - }{ - {Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`}, - {Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, - {Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, - { - Name("tagStruct"), tagStruct, - `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`, - `{"<>&#! ":0,"Invalid":0}`, - }, - { - Name(`""`), marshalerStruct, - `{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`, - `{"NonPtr":"","Ptr":""}`, - }, - { - Name("stringOption"), stringOption, - `{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`, - `{"bar":"\"foobar\""}`, - }, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - var buf strings.Builder - enc := NewEncoder(&buf) - if err := enc.Encode(tt.v); err != nil { - t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err) - } - if got := strings.TrimSpace(buf.String()); got != tt.wantEscape { - t.Errorf("%s: Encode(%s):\n\tgot: %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape) - } - buf.Reset() - enc.SetEscapeHTML(false) - if err := enc.Encode(tt.v); err != nil { - t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err) - } - if got := strings.TrimSpace(buf.String()); got != tt.want { - t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot: %s\n\twant: %s", - tt.Where, tt.Name, got, tt.want) - } - }) - } -} - -func TestDecoder(t *testing.T) { - for i := 0; i <= len(streamTest); i++ { - // Use stream without newlines as input, - // just to stress the decoder even more. - // Our test input does not include back-to-back numbers. - // Otherwise stripping the newlines would - // merge two adjacent JSON values. - var buf bytes.Buffer - for _, c := range nlines(streamEncoded, i) { - if c != '\n' { - buf.WriteRune(c) - } - } - out := make([]any, i) - dec := NewDecoder(&buf) - for j := range out { - if err := dec.Decode(&out[j]); err != nil { - t.Fatalf("decode #%d/%d error: %v", j, i, err) - } - } - if !reflect.DeepEqual(out, streamTest[0:i]) { - t.Errorf("decoding %d items: mismatch:", i) - for j := range out { - if !reflect.DeepEqual(out[j], streamTest[j]) { - t.Errorf("#%d:\n\tgot: %v\n\twant: %v", j, out[j], streamTest[j]) - } - } - break - } - } -} - -func TestDecoderBuffered(t *testing.T) { - r := strings.NewReader(`{"Name": "Gopher"} extra `) - var m struct { - Name string - } - d := NewDecoder(r) - err := d.Decode(&m) - if err != nil { - t.Fatal(err) - } - if m.Name != "Gopher" { - t.Errorf("Name = %s, want Gopher", m.Name) - } - rest, err := io.ReadAll(d.Buffered()) - if err != nil { - t.Fatal(err) - } - if got, want := string(rest), " extra "; got != want { - t.Errorf("Remaining = %s, want %s", got, want) - } -} - -func nlines(s string, n int) string { - if n <= 0 { - return "" - } - for i, c := range s { - if c == '\n' { - if n--; n == 0 { - return s[0 : i+1] - } - } - } - return s -} - -func TestRawMessage(t *testing.T) { - var data struct { - X float64 - Id RawMessage - Y float32 - } - const raw = `["\u0056",null]` - const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` - err := Unmarshal([]byte(want), &data) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if string([]byte(data.Id)) != raw { - t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", []byte(data.Id), raw) - } - got, err := Marshal(&data) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestNullRawMessage(t *testing.T) { - var data struct { - X float64 - Id RawMessage - IdPtr *RawMessage - Y float32 - } - const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}` - err := Unmarshal([]byte(want), &data) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if want, got := "null", string(data.Id); want != got { - t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", got, want) - } - if data.IdPtr != nil { - t.Fatalf("pointer mismatch: got non-nil, want nil") - } - got, err := Marshal(&data) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestBlocking(t *testing.T) { - tests := []struct { - CaseName - in string - }{ - {Name(""), `{"x": 1}`}, - {Name(""), `[1, 2, 3]`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - r, w := net.Pipe() - go w.Write([]byte(tt.in)) - var val any - - // If Decode reads beyond what w.Write writes above, - // it will block, and the test will deadlock. - if err := NewDecoder(r).Decode(&val); err != nil { - t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err) - } - r.Close() - w.Close() - }) - } -} - -type decodeThis struct { - v any -} - -func TestDecodeInStream(t *testing.T) { - tests := []struct { - CaseName - json string - expTokens []any - }{ - // streaming token cases - {CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}}, - {CaseName: Name(""), json: ` [10] `, expTokens: []any{ - Delim('['), float64(10), Delim(']')}}, - {CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{ - Delim('['), false, float64(10), "b", Delim(']')}}, - {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{ - Delim('{'), "a", float64(1), Delim('}')}}, - {CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{ - Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, - {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ - Delim('['), - Delim('{'), "a", float64(1), Delim('}'), - Delim('{'), "a", float64(2), Delim('}'), - Delim(']')}}, - {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{ - Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), - Delim('}')}}, - {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{ - Delim('{'), "obj", Delim('['), - Delim('{'), "a", float64(1), Delim('}'), - Delim(']'), Delim('}')}}, - - // streaming tokens with intermittent Decode() - {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{ - Delim('{'), "a", - decodeThis{float64(1)}, - Delim('}')}}, - {CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - Delim(']')}}, - {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - decodeThis{map[string]any{"a": float64(2)}}, - Delim(']')}}, - {CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ - Delim('{'), "obj", Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - Delim(']'), Delim('}')}}, - - {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{ - Delim('{'), "obj", - decodeThis{map[string]any{"a": float64(1)}}, - Delim('}')}}, - {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{ - Delim('{'), "obj", - decodeThis{[]any{ - map[string]any{"a": float64(1)}, - }}, - Delim('}')}}, - {CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - decodeThis{&SyntaxError{"expected comma after array element", 11}}, - }}, - {CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{ - Delim('{'), strings.Repeat("a", 513), - decodeThis{&SyntaxError{"expected colon after object key", 518}}, - }}, - {CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{ - Delim('{'), - &SyntaxError{"invalid character 'a' in string escape code", 3}, - }}, - {CaseName: Name(""), json: ` \a`, expTokens: []any{ - &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1}, - }}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - dec := NewDecoder(strings.NewReader(tt.json)) - for i, want := range tt.expTokens { - var got any - var err error - - if dt, ok := want.(decodeThis); ok { - want = dt.v - err = dec.Decode(&got) - } else { - got, err = dec.Token() - } - if errWant, ok := want.(error); ok { - if err == nil || !reflect.DeepEqual(err, errWant) { - t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant) - } - break - } else if err != nil { - t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err) - } - if !reflect.DeepEqual(got, want) { - t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot: %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want) - } - } - }) - } -} - -// Test from golang.org/issue/11893 -func TestHTTPDecoding(t *testing.T) { - const raw = `{ "foo": "bar" }` - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(raw)) - })) - defer ts.Close() - res, err := http.Get(ts.URL) - if err != nil { - log.Fatalf("http.Get error: %v", err) - } - defer res.Body.Close() - - foo := struct { - Foo string - }{} - - d := NewDecoder(res.Body) - err = d.Decode(&foo) - if err != nil { - t.Fatalf("Decode error: %v", err) - } - if foo.Foo != "bar" { - t.Errorf(`Decode: got %q, want "bar"`, foo.Foo) - } - - // make sure we get the EOF the second time - err = d.Decode(&foo) - if err != io.EOF { - t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err) - } -} diff --git a/pkg/encoders/json/tagkey_test.go b/pkg/encoders/json/tagkey_test.go deleted file mode 100644 index 8e4d360..0000000 --- a/pkg/encoders/json/tagkey_test.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2011 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. - -//go:build !goexperiment.jsonv2 - -package json - -import ( - "testing" -) - -type basicLatin2xTag struct { - V string `json:"$%-/"` -} - -type basicLatin3xTag struct { - V string `json:"0123456789"` -} - -type basicLatin4xTag struct { - V string `json:"ABCDEFGHIJKLMO"` -} - -type basicLatin5xTag struct { - V string `json:"PQRSTUVWXYZ_"` -} - -type basicLatin6xTag struct { - V string `json:"abcdefghijklmno"` -} - -type basicLatin7xTag struct { - V string `json:"pqrstuvwxyz"` -} - -type miscPlaneTag struct { - V string `json:"色は匂へど"` -} - -type percentSlashTag struct { - V string `json:"text/html%"` // https://golang.org/issue/2718 -} - -type punctuationTag struct { - V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` // https://golang.org/issue/3546 -} - -type dashTag struct { - V string `json:"-,"` -} - -type emptyTag struct { - W string -} - -type misnamedTag struct { - X string `jsom:"Misnamed"` -} - -type badFormatTag struct { - Y string `:"BadFormat"` -} - -type badCodeTag struct { - Z string `json:" !\"#&'()*+,."` -} - -type spaceTag struct { - Q string `json:"With space"` -} - -type unicodeTag struct { - W string `json:"Ελλάδα"` -} - -func TestStructTagObjectKey(t *testing.T) { - tests := []struct { - CaseName - raw any - value string - key string - }{ - {Name(""), basicLatin2xTag{"2x"}, "2x", "$%-/"}, - {Name(""), basicLatin3xTag{"3x"}, "3x", "0123456789"}, - {Name(""), basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, - {Name(""), basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, - {Name(""), basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, - {Name(""), basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, - {Name(""), miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, - {Name(""), dashTag{"foo"}, "foo", "-"}, - {Name(""), emptyTag{"Pour Moi"}, "Pour Moi", "W"}, - {Name(""), misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, - {Name(""), badFormatTag{"Orfevre"}, "Orfevre", "Y"}, - {Name(""), badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, - {Name(""), percentSlashTag{"brut"}, "brut", "text/html%"}, - {Name(""), punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, - {Name(""), spaceTag{"Perreddu"}, "Perreddu", "With space"}, - {Name(""), unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.raw) - if err != nil { - t.Fatalf("%s: Marshal error: %v", tt.Where, err) - } - var f any - err = Unmarshal(b, &f) - if err != nil { - t.Fatalf("%s: Unmarshal error: %v", tt.Where, err) - } - for k, v := range f.(map[string]any) { - if k == tt.key { - if s, ok := v.(string); !ok || s != tt.value { - t.Fatalf("%s: Unmarshal(%#q) value:\n\tgot: %q\n\twant: %q", tt.Where, b, s, tt.value) - } - } else { - t.Fatalf("%s: Unmarshal(%#q): unexpected key: %q", tt.Where, b, k) - } - } - }) - } -} diff --git a/pkg/encoders/json/tags_test.go b/pkg/encoders/json/tags_test.go deleted file mode 100644 index 6bb621c..0000000 --- a/pkg/encoders/json/tags_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2011 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. - -//go:build !goexperiment.jsonv2 - -package json - -import "testing" - -func TestTagParsing(t *testing.T) { - name, opts := parseTag("field,foobar,foo") - if name != "field" { - t.Fatalf("name = %q, want field", name) - } - for _, tt := range []struct { - opt string - want bool - }{ - {"foobar", true}, - {"foo", true}, - {"bar", false}, - } { - if opts.Contains(tt.opt) != tt.want { - t.Errorf("Contains(%q) = %v, want %v", tt.opt, !tt.want, tt.want) - } - } -} diff --git a/pkg/encoders/json/v2_bench_test.go b/pkg/encoders/json/v2_bench_test.go deleted file mode 100644 index b9ed7b6..0000000 --- a/pkg/encoders/json/v2_bench_test.go +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright 2011 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. - -//go:build goexperiment.jsonv2 - -// Large data benchmark. -// The JSON data is a summary of agl's changes in the -// go, webkit, and chromium open source projects. -// We benchmark converting between the JSON form -// and in-memory data structures. - -package json - -import ( - "bytes" - "io" - "strings" - "testing" - - "encoding/json/internal/jsontest" -) - -type codeResponse struct { - Tree *codeNode `json:"tree"` - Username string `json:"username"` -} - -type codeNode struct { - Name string `json:"name"` - Kids []*codeNode `json:"kids"` - CLWeight float64 `json:"cl_weight"` - Touches int `json:"touches"` - MinT int64 `json:"min_t"` - MaxT int64 `json:"max_t"` - MeanT int64 `json:"mean_t"` -} - -var codeJSON []byte -var codeStruct codeResponse - -func codeInit() { - var data []byte - for _, entry := range jsontest.Data { - if entry.Name == "GolangSource" { - data = entry.Data() - } - } - codeJSON = data - - if err := Unmarshal(codeJSON, &codeStruct); err != nil { - panic("unmarshal code.json: " + err.Error()) - } - - var err error - if data, err = Marshal(&codeStruct); err != nil { - panic("marshal code.json: " + err.Error()) - } - - if !bytes.Equal(data, codeJSON) { - println("different lengths", len(data), len(codeJSON)) - for i := 0; i < len(data) && i < len(codeJSON); i++ { - if data[i] != codeJSON[i] { - println("re-marshal: changed at byte", i) - println("orig: ", string(codeJSON[i-10:i+10])) - println("new: ", string(data[i-10:i+10])) - break - } - } - panic("re-marshal code.json: different result") - } -} - -func BenchmarkCodeEncoder(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - enc := NewEncoder(io.Discard) - for pb.Next() { - if err := enc.Encode(&codeStruct); err != nil { - b.Fatalf("Encode error: %v", err) - } - } - }) - 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.Fatalf("Encode error: %v", err) - } - if _, err := Marshal(dummy); err == nil { - b.Fatal("Marshal error: got nil, want non-nil") - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkCodeMarshal(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if _, err := Marshal(&codeStruct); err != nil { - b.Fatalf("Marshal error: %v", err) - } - } - }) - 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.Fatalf("Marshal error: %v", err) - } - if _, err := Marshal(dummy); err == nil { - b.Fatal("Marshal error: got nil, want non-nil") - } - } - }) - 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 - // interface parameter to Marshal. - v := &struct { - Bytes []byte - }{ - bytes.Repeat(sample, (n/len(sample))+1)[:n], - } - return func(b *testing.B) { - for i := 0; i < b.N; i++ { - if _, err := Marshal(v); err != nil { - b.Fatalf("Marshal error: %v", err) - } - } - } -} - -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.Fatalf("Marshal error: %v", err) - } - if _, err := Marshal(dummy); err == nil { - b.Fatal("Marshal error: got nil, want non-nil") - } - } - } -} - -func BenchmarkMarshalBytes(b *testing.B) { - b.ReportAllocs() - // 32 fits within encodeState.scratch. - b.Run("32", benchMarshalBytes(32)) - // 256 doesn't fit in encodeState.scratch, but is small enough to - // allocate and avoid the slower base64.NewEncoder. - b.Run("256", benchMarshalBytes(256)) - // 4096 is large enough that we want to avoid allocating for it. - 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 BenchmarkMarshalMap(b *testing.B) { - b.ReportAllocs() - m := map[string]int{ - "key3": 3, - "key2": 2, - "key1": 1, - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if _, err := Marshal(m); err != nil { - b.Fatal("Marshal:", err) - } - } - }) -} - -func BenchmarkCodeDecoder(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - var buf bytes.Buffer - dec := NewDecoder(&buf) - var r codeResponse - for pb.Next() { - buf.Write(codeJSON) - // hide EOF - buf.WriteByte('\n') - buf.WriteByte('\n') - buf.WriteByte('\n') - if err := dec.Decode(&r); err != nil { - b.Fatalf("Decode error: %v", err) - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkUnicodeDecoder(b *testing.B) { - b.ReportAllocs() - j := []byte(`"\uD83D\uDE01"`) - b.SetBytes(int64(len(j))) - r := bytes.NewReader(j) - dec := NewDecoder(r) - var out string - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := dec.Decode(&out); err != nil { - b.Fatalf("Decode error: %v", err) - } - r.Seek(0, 0) - } -} - -func BenchmarkDecoderStream(b *testing.B) { - b.ReportAllocs() - b.StopTimer() - var buf bytes.Buffer - dec := NewDecoder(&buf) - buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") - var x any - if err := dec.Decode(&x); err != nil { - b.Fatalf("Decode error: %v", err) - } - ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" - b.StartTimer() - for i := 0; i < b.N; i++ { - if i%300000 == 0 { - buf.WriteString(ones) - } - x = nil - switch err := dec.Decode(&x); { - case err != nil: - b.Fatalf("Decode error: %v", err) - case x != 1.0: - b.Fatalf("Decode: got %v want 1.0", i) - } - } -} - -func BenchmarkCodeUnmarshal(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - var r codeResponse - if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkCodeUnmarshalReuse(b *testing.B) { - b.ReportAllocs() - if codeJSON == nil { - b.StopTimer() - codeInit() - b.StartTimer() - } - b.RunParallel(func(pb *testing.PB) { - var r codeResponse - for pb.Next() { - if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) - b.SetBytes(int64(len(codeJSON))) -} - -func BenchmarkUnmarshalString(b *testing.B) { - b.ReportAllocs() - data := []byte(`"hello, world"`) - b.RunParallel(func(pb *testing.PB) { - var s string - for pb.Next() { - if err := Unmarshal(data, &s); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmarshalFloat64(b *testing.B) { - b.ReportAllocs() - data := []byte(`3.14`) - b.RunParallel(func(pb *testing.PB) { - var f float64 - for pb.Next() { - if err := Unmarshal(data, &f); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmarshalInt64(b *testing.B) { - b.ReportAllocs() - data := []byte(`3`) - b.RunParallel(func(pb *testing.PB) { - var x int64 - for pb.Next() { - if err := Unmarshal(data, &x); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmarshalMap(b *testing.B) { - b.ReportAllocs() - data := []byte(`{"key1":"value1","key2":"value2","key3":"value3"}`) - b.RunParallel(func(pb *testing.PB) { - x := make(map[string]string, 3) - for pb.Next() { - if err := Unmarshal(data, &x); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkIssue10335(b *testing.B) { - b.ReportAllocs() - j := []byte(`{"a":{ }}`) - b.RunParallel(func(pb *testing.PB) { - var s struct{} - for pb.Next() { - if err := Unmarshal(j, &s); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkIssue34127(b *testing.B) { - b.ReportAllocs() - j := struct { - Bar string `json:"bar,string"` - }{ - Bar: `foobar`, - } - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - if _, err := Marshal(&j); err != nil { - b.Fatalf("Marshal error: %v", err) - } - } - }) -} - -func BenchmarkUnmapped(b *testing.B) { - b.ReportAllocs() - j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`) - b.RunParallel(func(pb *testing.PB) { - var s struct{} - for pb.Next() { - if err := Unmarshal(j, &s); err != nil { - b.Fatalf("Unmarshal error: %v", err) - } - } - }) -} - -func BenchmarkEncodeMarshaler(b *testing.B) { - b.ReportAllocs() - - m := struct { - A int - B RawMessage - }{} - - b.RunParallel(func(pb *testing.PB) { - enc := NewEncoder(io.Discard) - - for pb.Next() { - if err := enc.Encode(&m); err != nil { - b.Fatalf("Encode error: %v", err) - } - } - }) -} - -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.Fatalf("Encode error: %v", err) - } - } - }) -} diff --git a/pkg/encoders/json/v2_decode_test.go b/pkg/encoders/json/v2_decode_test.go deleted file mode 100644 index 1e4914e..0000000 --- a/pkg/encoders/json/v2_decode_test.go +++ /dev/null @@ -1,2835 +0,0 @@ -// Copyright 2010 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. - -//go:build goexperiment.jsonv2 - -package json - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "image" - "io" - "maps" - "math" - "math/big" - "net" - "reflect" - "slices" - "strconv" - "strings" - "testing" - "time" -) - -func len64(s string) int64 { - return int64(len(s)) -} - -type T struct { - X string - Y int - Z int `json:"-"` -} - -type U struct { - Alphabet string `json:"alpha"` -} - -type V struct { - F1 any - F2 int32 - F3 Number - F4 *VOuter -} - -type VOuter struct { - V V -} - -type W struct { - S SS -} - -type P struct { - PP PP -} - -type PP struct { - T T - Ts []T -} - -type SS string - -func (*SS) UnmarshalJSON(data []byte) error { - return &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[SS]()} -} - -type TAlias T - -func (tt *TAlias) UnmarshalJSON(data []byte) error { - t := T{} - if err := Unmarshal(data, &t); err != nil { - return err - } - *tt = TAlias(t) - return nil -} - -type TOuter struct { - T TAlias -} - -// ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and -// without UseNumber -var ifaceNumAsFloat64 = map[string]any{ - "k1": float64(1), - "k2": "s", - "k3": []any{float64(1), float64(2.0), float64(3e-3)}, - "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, -} - -var ifaceNumAsNumber = map[string]any{ - "k1": Number("1"), - "k2": "s", - "k3": []any{Number("1"), Number("2.0"), Number("3e-3")}, - "k4": map[string]any{"kk1": "s", "kk2": Number("2")}, -} - -type tx struct { - x int -} - -type u8 uint8 - -// A type that can unmarshal itself. - -type unmarshaler struct { - T bool -} - -func (u *unmarshaler) UnmarshalJSON(b []byte) error { - *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. - return nil -} - -type ustruct struct { - M unmarshaler -} - -type unmarshalerText struct { - A, B string -} - -// needed for re-marshaling tests -func (u unmarshalerText) MarshalText() ([]byte, error) { - return []byte(u.A + ":" + u.B), nil -} - -func (u *unmarshalerText) UnmarshalText(b []byte) error { - pos := bytes.IndexByte(b, ':') - if pos == -1 { - return errors.New("missing separator") - } - u.A, u.B = string(b[:pos]), string(b[pos+1:]) - return nil -} - -var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) - -type ustructText struct { - M unmarshalerText -} - -// u8marshal is an integer type that can marshal/unmarshal itself. -type u8marshal uint8 - -func (u8 u8marshal) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("u%d", u8)), nil -} - -var errMissingU8Prefix = errors.New("missing 'u' prefix") - -func (u8 *u8marshal) UnmarshalText(b []byte) error { - if !bytes.HasPrefix(b, []byte{'u'}) { - return errMissingU8Prefix - } - n, err := strconv.Atoi(string(b[1:])) - if err != nil { - return err - } - *u8 = u8marshal(n) - return nil -} - -var _ encoding.TextUnmarshaler = (*u8marshal)(nil) - -var ( - umtrue = unmarshaler{true} - umslice = []unmarshaler{{true}} - umstruct = ustruct{unmarshaler{true}} - - umtrueXY = unmarshalerText{"x", "y"} - umsliceXY = []unmarshalerText{{"x", "y"}} - umstructXY = ustructText{unmarshalerText{"x", "y"}} - - ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} -) - -// Test data structures for anonymous fields. - -type Point struct { - Z int -} - -type Top struct { - Level0 int - Embed0 - *Embed0a - *Embed0b `json:"e,omitempty"` // treated as named - Embed0c `json:"-"` // ignored - Loop - Embed0p // has Point with X, Y, used - Embed0q // has Point with Z, used - embed // contains exported field -} - -type Embed0 struct { - Level1a int // overridden by Embed0a's Level1a with json tag - Level1b int // used because Embed0a's Level1b is renamed - Level1c int // used because Embed0a's Level1c is ignored - Level1d int // annihilated by Embed0a's Level1d - Level1e int `json:"x"` // annihilated by Embed0a.Level1e -} - -type Embed0a struct { - Level1a int `json:"Level1a,omitempty"` - Level1b int `json:"LEVEL1B,omitempty"` - Level1c int `json:"-"` - Level1d int // annihilated by Embed0's Level1d - Level1f int `json:"x"` // annihilated by Embed0's Level1e -} - -type Embed0b Embed0 - -type Embed0c Embed0 - -type Embed0p struct { - image.Point -} - -type Embed0q struct { - Point -} - -type embed struct { - Q int -} - -type Loop struct { - Loop1 int `json:",omitempty"` - Loop2 int `json:",omitempty"` - *Loop -} - -// From reflect test: -// The X in S6 and S7 annihilate, but they also block the X in S8.S9. -type S5 struct { - S6 - S7 - S8 -} - -type S6 struct { - X int -} - -type S7 S6 - -type S8 struct { - S9 -} - -type S9 struct { - X int - Y int -} - -// From reflect test: -// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. -type S10 struct { - S11 - S12 - S13 -} - -type S11 struct { - S6 -} - -type S12 struct { - S6 -} - -type S13 struct { - S8 -} - -type Ambig struct { - // Given "hello", the first match should win. - First int `json:"HELLO"` - Second int `json:"Hello"` -} - -type XYZ struct { - X any - Y any - Z any -} - -type unexportedWithMethods struct{} - -func (unexportedWithMethods) F() {} - -type byteWithMarshalJSON byte - -func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil -} - -func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalJSON(i) - return nil -} - -type byteWithPtrMarshalJSON byte - -func (b *byteWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return byteWithMarshalJSON(*b).MarshalJSON() -} - -func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type byteWithMarshalText byte - -func (b byteWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil -} - -func (b *byteWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalText(i) - return nil -} - -type byteWithPtrMarshalText byte - -func (b *byteWithPtrMarshalText) MarshalText() ([]byte, error) { - return byteWithMarshalText(*b).MarshalText() -} - -func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*byteWithMarshalText)(b).UnmarshalText(data) -} - -type intWithMarshalJSON int - -func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil -} - -func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalJSON(i) - return nil -} - -type intWithPtrMarshalJSON int - -func (b *intWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return intWithMarshalJSON(*b).MarshalJSON() -} - -func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*intWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type intWithMarshalText int - -func (b intWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil -} - -func (b *intWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalText(i) - return nil -} - -type intWithPtrMarshalText int - -func (b *intWithPtrMarshalText) MarshalText() ([]byte, error) { - return intWithMarshalText(*b).MarshalText() -} - -func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*intWithMarshalText)(b).UnmarshalText(data) -} - -type mapStringToStringData struct { - Data map[string]string `json:"data"` -} - -type B struct { - B bool `json:",string"` -} - -type DoublePtr struct { - I **int - J **int -} - -var unmarshalTests = []struct { - CaseName - in string - ptr any // new(type) - out any - err error - useNumber bool - golden bool - disallowUnknownFields bool -}{ - // basic types - {CaseName: Name(""), in: `true`, ptr: new(bool), out: true}, - {CaseName: Name(""), in: `1`, ptr: new(int), out: 1}, - {CaseName: Name(""), in: `1.2`, ptr: new(float64), out: 1.2}, - {CaseName: Name(""), in: `-5`, ptr: new(int16), out: int16(-5)}, - {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, - {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2")}, - {CaseName: Name(""), in: `2`, ptr: new(any), out: float64(2.0)}, - {CaseName: Name(""), in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, - {CaseName: Name(""), in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, - {CaseName: Name(""), in: `"http:\/\/"`, ptr: new(string), out: "http://"}, - {CaseName: Name(""), in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, - {CaseName: Name(""), in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, - {CaseName: Name(""), in: "null", ptr: new(any), out: nil}, - {CaseName: Name(""), in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), len64(`{"X": `), "T", "X", nil}}, - {CaseName: Name(""), in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), len64(`{"X": `), "T", "X", nil}}, - {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, - {CaseName: Name(""), in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[SS](), 0, "", "", nil}}, - {CaseName: Name(""), in: `{"T": {"X": 23}}`, ptr: new(TOuter), out: TOuter{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), len64(`{"X": `), "T", "X", nil}}, - {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, - {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, - {CaseName: Name(""), 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 - {CaseName: Name(""), in: "\n true ", ptr: new(bool), out: true}, - {CaseName: Name(""), in: "\t 1 ", ptr: new(int), out: 1}, - {CaseName: Name(""), in: "\r 1.2 ", ptr: new(float64), out: 1.2}, - {CaseName: Name(""), in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, - {CaseName: Name(""), in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, - - // Z has a "-" tag. - {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, - {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}, err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, - - {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}, err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - {CaseName: Name(""), in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, - {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - - // syntax errors - {CaseName: Name(""), in: ``, ptr: new(any), err: &SyntaxError{errUnexpectedEnd.Error(), 0}}, - {CaseName: Name(""), in: " \n\r\t", ptr: new(any), err: &SyntaxError{errUnexpectedEnd.Error(), len64(" \n\r\t")}}, - {CaseName: Name(""), in: `[2, 3`, ptr: new(any), err: &SyntaxError{errUnexpectedEnd.Error(), len64(`[2, 3`)}}, - {CaseName: Name(""), in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", len64(`{"X": "foo", "Y"`)}}, - {CaseName: Name(""), in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", len64(`[1, 2, 3`)}}, - {CaseName: Name(""), in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", len64(`{"X":12`)}, useNumber: true}, - {CaseName: Name(""), in: `{"F3": -}`, ptr: new(V), err: &SyntaxError{"invalid character '}' in numeric literal", len64(`{"F3": -`)}}, - - // raw value errors - {CaseName: Name(""), in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, - {CaseName: Name(""), in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` 42 `)}}, - {CaseName: Name(""), in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, - {CaseName: Name(""), in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` false `)}}, - {CaseName: Name(""), in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, - {CaseName: Name(""), in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` 3.4 `)}}, - {CaseName: Name(""), in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, - {CaseName: Name(""), in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` "string" `)}}, - - // array tests - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, - {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, - - // empty array to interface test - {CaseName: Name(""), in: `[]`, ptr: new([]any), out: []any{}}, - {CaseName: Name(""), in: `null`, ptr: new([]any), out: []any(nil)}, - {CaseName: Name(""), in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, - {CaseName: Name(""), in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, - - // composite tests - {CaseName: Name(""), in: allValueIndent, ptr: new(All), out: allValue}, - {CaseName: Name(""), in: allValueCompact, ptr: new(All), out: allValue}, - {CaseName: Name(""), in: allValueIndent, ptr: new(*All), out: &allValue}, - {CaseName: Name(""), in: allValueCompact, ptr: new(*All), out: &allValue}, - {CaseName: Name(""), in: pallValueIndent, ptr: new(All), out: pallValue}, - {CaseName: Name(""), in: pallValueCompact, ptr: new(All), out: pallValue}, - {CaseName: Name(""), in: pallValueIndent, ptr: new(*All), out: &pallValue}, - {CaseName: Name(""), in: pallValueCompact, ptr: new(*All), out: &pallValue}, - - // unmarshal interface test - {CaseName: Name(""), in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called - {CaseName: Name(""), in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, - {CaseName: Name(""), in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, - {CaseName: Name(""), in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, - {CaseName: Name(""), in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, - - // UnmarshalText interface test - {CaseName: Name(""), in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, - {CaseName: Name(""), in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, - {CaseName: Name(""), in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, - {CaseName: Name(""), in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, - {CaseName: Name(""), in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, - - // integer-keyed map test - { - CaseName: Name(""), - in: `{"-1":"a","0":"b","1":"c"}`, - ptr: new(map[int]string), - out: map[int]string{-1: "a", 0: "b", 1: "c"}, - }, - { - CaseName: Name(""), - in: `{"0":"a","10":"c","9":"b"}`, - ptr: new(map[u8]string), - out: map[u8]string{0: "a", 9: "b", 10: "c"}, - }, - { - CaseName: Name(""), - in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, - ptr: new(map[int64]string), - out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, - }, - { - CaseName: Name(""), - in: `{"18446744073709551615":"max"}`, - ptr: new(map[uint64]string), - out: map[uint64]string{math.MaxUint64: "max"}, - }, - { - CaseName: Name(""), - in: `{"0":false,"10":true}`, - ptr: new(map[uintptr]bool), - out: map[uintptr]bool{0: false, 10: true}, - }, - - // Check that MarshalText and UnmarshalText take precedence - // over default integer handling in map keys. - { - CaseName: Name(""), - in: `{"u2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{2: 4}, - }, - { - CaseName: Name(""), - in: `{"2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{}, - err: errMissingU8Prefix, - }, - - // integer-keyed map errors - { - CaseName: Name(""), - in: `{"abc":"abc"}`, - ptr: new(map[int]string), - out: map[int]string{}, - err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeFor[int](), Field: "abc", Offset: len64(`{`)}, - }, - { - CaseName: Name(""), - in: `{"256":"abc"}`, - ptr: new(map[uint8]string), - out: map[uint8]string{}, - err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeFor[uint8](), Field: "256", Offset: len64(`{`)}, - }, - { - CaseName: Name(""), - in: `{"128":"abc"}`, - ptr: new(map[int8]string), - out: map[int8]string{}, - err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeFor[int8](), Field: "128", Offset: len64(`{`)}, - }, - { - CaseName: Name(""), - in: `{"-1":"abc"}`, - ptr: new(map[uint8]string), - out: map[uint8]string{}, - err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeFor[uint8](), Field: "-1", Offset: len64(`{`)}, - }, - { - CaseName: Name(""), - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[int]int), - out: map[string]map[int]int{"F": {3: 4}}, - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[int](), Field: "F.a", Offset: len64(`{"F":{`)}, - }, - { - CaseName: Name(""), - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[uint]int), - out: map[string]map[uint]int{"F": {3: 4}}, - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[uint](), Field: "F.a", Offset: len64(`{"F":{`)}, - }, - - // Map keys can be encoding.TextUnmarshalers. - {CaseName: Name(""), in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - // If multiple values for the same key exists, only the most recent value is used. - {CaseName: Name(""), in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - - { - CaseName: Name(""), - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - }, - }, - { - CaseName: Name(""), - in: `{"hello": 1}`, - ptr: new(Ambig), - out: Ambig{First: 1}, - }, - - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9: S9{Y: 2}}}, - }, - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9{Y: 2}}}, - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, - }, - { - CaseName: Name(""), - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8{S9{Y: 2}}}}, - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - CaseName: Name(""), - in: `{"I": 0, "I": null, "J": null}`, - ptr: new(DoublePtr), - out: DoublePtr{I: nil, J: nil}, - }, - - // invalid UTF-8 is coerced to valid UTF-8. - { - CaseName: Name(""), - in: "\"hello\xffworld\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\xc2\xc2world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\xc2\xffworld\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\\ud800world\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - CaseName: Name(""), - in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", - ptr: new(string), - out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", - }, - - // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. - { - CaseName: Name(""), - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[time.Time]string), - out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, - }, - - // issue 8305 - { - CaseName: Name(""), - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[Point]string), - out: map[Point]string{}, - err: &UnmarshalTypeError{Value: "string", Type: reflect.TypeFor[Point](), Field: `2009-11-10T23:00:00Z`, Offset: len64(`{`)}, - }, - { - CaseName: Name(""), - in: `{"asdf": "hello world"}`, - ptr: new(map[unmarshaler]string), - out: map[unmarshaler]string{}, - err: &UnmarshalTypeError{Value: "string", Type: reflect.TypeFor[unmarshaler](), Field: "asdf", Offset: len64(`{`)}, - }, - - // related to issue 13783. - // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, - // similar to marshaling a slice of typed int. - // These tests check that, assuming the byte type also has valid decoding methods, - // either the old base64 string encoding or the new per-element encoding can be - // successfully unmarshaled. The custom unmarshalers were accessible in earlier - // versions of Go, even though the custom marshaler was not. - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - golden: true, - }, - - // ints work with the marshaler but not the base64 []byte case - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalJSON), - out: []intWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalText), - out: []intWithMarshalText{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalJSON), - out: []intWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - CaseName: Name(""), - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalText), - out: []intWithPtrMarshalText{1, 2, 3}, - golden: true, - }, - - {CaseName: Name(""), in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, - {CaseName: Name(""), in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, - {CaseName: Name(""), in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, - {CaseName: Name(""), in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, - {CaseName: Name(""), in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, - {CaseName: Name(""), in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, - {CaseName: Name(""), in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, - {CaseName: Name(""), in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, - {CaseName: Name(""), in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, - {CaseName: Name(""), in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, - {CaseName: Name(""), in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, - - { - CaseName: Name(""), - in: `{"V": {"F2": "hello"}}`, - ptr: new(VOuter), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "VOuter", - Field: "V.F2", - Type: reflect.TypeFor[int32](), - Offset: len64(`{"V": {"F2": `), - }, - }, - { - CaseName: Name(""), - in: `{"V": {"F4": {}, "F2": "hello"}}`, - ptr: new(VOuter), - out: VOuter{V: V{F4: &VOuter{}}}, - err: &UnmarshalTypeError{ - Value: "string", - Struct: "VOuter", - Field: "V.F2", - Type: reflect.TypeFor[int32](), - Offset: len64(`{"V": {"F4": {}, "F2": `), - }, - }, - - { - CaseName: Name(""), - in: `{"Level1a": "hello"}`, - ptr: new(Top), - out: Top{Embed0a: &Embed0a{}}, - err: &UnmarshalTypeError{ - Value: "string", - Struct: "Top", - Field: "Level1a", - Type: reflect.TypeFor[int](), - Offset: len64(`{"Level1a": `), - }, - }, - - // issue 15146. - // invalid inputs in wrongStringTests below. - {CaseName: Name(""), in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, - {CaseName: Name(""), in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, - {CaseName: Name(""), in: `{"B": "maybe"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "maybe"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, - {CaseName: Name(""), in: `{"B": "tru"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "tru"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, - {CaseName: Name(""), in: `{"B": "False"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "False"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, - {CaseName: Name(""), in: `{"B": "null"}`, ptr: new(B), out: B{false}}, - {CaseName: Name(""), in: `{"B": "nul"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "nul"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, - {CaseName: Name(""), in: `{"B": [2, 3]}`, ptr: new(B), err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `)}}, - - // additional tests for disallowUnknownFields - { - CaseName: Name(""), - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18, - "extra": true - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{Level1a: 5, Level1b: 6}, - Embed0b: &Embed0b{Level1a: 8, Level1b: 9, Level1c: 10, Level1d: 11, Level1e: 12}, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - Loop: nil, - }, - Embed0p: Embed0p{ - Point: image.Point{ - X: 15, - Y: 16, - }, - }, - Embed0q: Embed0q{Point: Point{Z: 17}}, - embed: embed{Q: 18}, - }, - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - { - CaseName: Name(""), - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12, - "extra": null - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{Level1a: 5, Level1b: 6}, - Embed0b: &Embed0b{Level1a: 8, Level1b: 9, Level1c: 10, Level1d: 11, Level1e: 12}, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - Loop: nil, - }, - Embed0p: Embed0p{ - Point: image.Point{ - X: 15, - Y: 16, - }, - }, - Embed0q: Embed0q{Point: Point{Z: 17}}, - embed: embed{Q: 18}, - }, - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - // issue 26444 - // UnmarshalTypeError without field & struct values - { - CaseName: Name(""), - in: `{"data":{"test1": "bob", "test2": 123}}`, - ptr: new(mapStringToStringData), - out: mapStringToStringData{map[string]string{"test1": "bob", "test2": ""}}, - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: len64(`{"data":{"test1": "bob", "test2": `), Struct: "mapStringToStringData", Field: "data.test2"}, - }, - { - CaseName: Name(""), - in: `{"data":{"test1": 123, "test2": "bob"}}`, - ptr: new(mapStringToStringData), - out: mapStringToStringData{Data: map[string]string{"test1": "", "test2": "bob"}}, - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: len64(`{"data":{"test1": `), Struct: "mapStringToStringData", Field: "data.test1"}, - }, - - // trying to decode JSON arrays or objects via TextUnmarshaler - { - CaseName: Name(""), - in: `[1, 2, 3]`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeFor[MustNotUnmarshalText](), Err: errors.New("JSON value must be string type")}, - }, - { - CaseName: Name(""), - in: `{"foo": "bar"}`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[MustNotUnmarshalText](), Err: errors.New("JSON value must be string type")}, - }, - // #22369 - { - CaseName: Name(""), - in: `{"PP": {"T": {"Y": "bad-type"}}}`, - ptr: new(P), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "P", - Field: "PP.T.Y", - Type: reflect.TypeFor[int](), - Offset: len64(`{"PP": {"T": {"Y": `), - }, - }, - { - CaseName: Name(""), - in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, - ptr: new(PP), - out: PP{Ts: []T{{Y: 1}, {Y: 2}, {Y: 0}}}, - err: &UnmarshalTypeError{ - Value: "string", - Struct: "PP", - Field: "Ts.2.Y", - Type: reflect.TypeFor[int](), - Offset: len64(`{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": `), - }, - }, - // #14702 - { - CaseName: Name(""), - in: `invalid`, - ptr: new(Number), - err: &SyntaxError{ - msg: "invalid character 'i' looking for beginning of value", - Offset: len64(``), - }, - }, - { - CaseName: Name(""), - in: `"invalid"`, - ptr: new(Number), - err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, - }, - { - CaseName: Name(""), - in: `{"A":"invalid"}`, - ptr: new(struct{ A Number }), - err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, - }, - { - CaseName: Name(""), - in: `{"A":"invalid"}`, - ptr: new(struct { - A Number `json:",string"` - }), - err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, - }, - { - CaseName: Name(""), - in: `{"A":"invalid"}`, - ptr: new(map[string]Number), - out: map[string]Number{"A": ""}, - err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, - }, - - { - CaseName: Name(""), - in: `5`, - ptr: new(Number), - out: Number("5"), - }, - { - CaseName: Name(""), - in: `"5"`, - ptr: new(Number), - out: Number("5"), - }, - { - CaseName: Name(""), - in: `{"N":5}`, - ptr: new(struct{ N Number }), - out: struct{ N Number }{"5"}, - }, - { - CaseName: Name(""), - in: `{"N":"5"}`, - ptr: new(struct{ N Number }), - out: struct{ N Number }{"5"}, - }, - { - CaseName: Name(""), - in: `{"N":5}`, - ptr: new(struct { - N Number `json:",string"` - }), - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[Number]()}, - }, - { - CaseName: Name(""), - in: `{"N":"5"}`, - ptr: new(struct { - N Number `json:",string"` - }), - out: struct { - N Number `json:",string"` - }{"5"}, - }, - - // Verify that syntactic errors are immediately fatal, - // while semantic errors are lazily reported - // (i.e., allow processing to continue). - { - CaseName: Name(""), - in: `[1,2,true,4,5}`, - ptr: new([]int), - err: &SyntaxError{msg: "invalid character '}' after array element", Offset: len64(`[1,2,true,4,5`)}, - }, - { - CaseName: Name(""), - in: `[1,2,true,4,5]`, - ptr: new([]int), - out: []int{1, 2, 0, 4, 5}, - err: &UnmarshalTypeError{Value: "bool", Type: reflect.TypeFor[int](), Field: "2", Offset: len64(`[1,2,`)}, - }, - - { - CaseName: Name("DashComma"), - in: `{"-":"hello"}`, - ptr: new(struct { - F string `json:"-,"` - }), - out: struct { - F string `json:"-,"` - }{"hello"}, - }, - { - CaseName: Name("DashCommaOmitEmpty"), - in: `{"-":"hello"}`, - ptr: new(struct { - F string `json:"-,omitempty"` - }), - out: struct { - F string `json:"-,omitempty"` - }{"hello"}, - }, -} - -func TestMarshal(t *testing.T) { - b, err := Marshal(allValue) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(b) != allValueCompact { - t.Errorf("Marshal:") - diff(t, b, []byte(allValueCompact)) - return - } - - b, err = Marshal(pallValue) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(b) != pallValueCompact { - t.Errorf("Marshal:") - diff(t, b, []byte(pallValueCompact)) - return - } -} - -func TestMarshalInvalidUTF8(t *testing.T) { - tests := []struct { - CaseName - in string - want string - }{ - {Name(""), "hello\xffworld", "\"hello\ufffdworld\""}, - {Name(""), "", `""`}, - {Name(""), "\xff", "\"\ufffd\""}, - {Name(""), "\xff\xff", "\"\ufffd\ufffd\""}, - {Name(""), "a\xffb", "\"a\ufffdb\""}, - {Name(""), "\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", "\"日本\ufffd\ufffd\ufffd\""}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - got, err := Marshal(tt.in) - if string(got) != tt.want || err != nil { - t.Errorf("%s: Marshal(%q):\n\tgot: (%q, %v)\n\twant: (%q, nil)", tt.Where, tt.in, got, err, tt.want) - } - }) - } -} - -func TestMarshalNumberZeroVal(t *testing.T) { - var n Number - out, err := Marshal(n) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - got := string(out) - if got != "0" { - t.Fatalf("Marshal: got %s, want 0", got) - } -} - -func TestMarshalEmbeds(t *testing.T) { - top := &Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - } - got, err := Marshal(top) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func equalError(a, b error) bool { - isJSONError := func(err error) bool { - switch err.(type) { - case - *InvalidUTF8Error, - *InvalidUnmarshalError, - *MarshalerError, - *SyntaxError, - *UnmarshalFieldError, - *UnmarshalTypeError, - *UnsupportedTypeError, - *UnsupportedValueError: - return true - } - return false - } - - if a == nil || b == nil { - return a == nil && b == nil - } - if isJSONError(a) || isJSONError(b) { - return reflect.DeepEqual(a, b) // safe for locally defined error types - } - return a.Error() == b.Error() -} - -func TestUnmarshal(t *testing.T) { - for _, tt := range unmarshalTests { - t.Run(tt.Name, func(t *testing.T) { - in := []byte(tt.in) - if err := checkValid(in); err != nil { - if !equalError(err, tt.err) { - t.Fatalf("%s: checkValid error:\n\tgot %#v\n\twant %#v", tt.Where, err, tt.err) - } - } - if tt.ptr == nil { - return - } - - typ := reflect.TypeOf(tt.ptr) - if typ.Kind() != reflect.Pointer { - t.Fatalf("%s: unmarshalTest.ptr %T is not a pointer type", tt.Where, tt.ptr) - } - typ = typ.Elem() - - // v = new(right-type) - v := reflect.New(typ) - - if !reflect.DeepEqual(tt.ptr, v.Interface()) { - // There's no reason for ptr to point to non-zero data, - // as we decode into new(right-type), so the data is - // discarded. - // This can easily mean tests that silently don't test - // what they should. To test decoding into existing - // data, see TestPrefilled. - t.Fatalf("%s: unmarshalTest.ptr %#v is not a pointer to a zero value", tt.Where, tt.ptr) - } - - dec := NewDecoder(bytes.NewReader(in)) - if tt.useNumber { - dec.UseNumber() - } - if tt.disallowUnknownFields { - dec.DisallowUnknownFields() - } - if tt.err != nil && strings.Contains(tt.err.Error(), errUnexpectedEnd.Error()) { - // In streaming mode, we expect EOF or ErrUnexpectedEOF instead. - if strings.TrimSpace(tt.in) == "" { - tt.err = io.EOF - } else { - tt.err = io.ErrUnexpectedEOF - } - } - if err := dec.Decode(v.Interface()); !equalError(err, tt.err) { - t.Fatalf("%s: Decode error:\n\tgot: %v\n\twant: %v\n\n\tgot: %#v\n\twant: %#v", tt.Where, err, tt.err, err, tt.err) - } else if err != nil && tt.out == nil { - // Initialize tt.out during an error where there are no mutations, - // so the output is just the zero value of the input type. - tt.out = reflect.Zero(v.Elem().Type()).Interface() - } - if got := v.Elem().Interface(); !reflect.DeepEqual(got, tt.out) { - gotJSON, _ := Marshal(got) - wantJSON, _ := Marshal(tt.out) - t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", tt.Where, got, tt.out, gotJSON, wantJSON) - } - - // Check round trip also decodes correctly. - if tt.err == nil { - enc, err := Marshal(v.Interface()) - if err != nil { - t.Fatalf("%s: Marshal error after roundtrip: %v", tt.Where, err) - } - if tt.golden && !bytes.Equal(enc, in) { - t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, enc, in) - } - vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = NewDecoder(bytes.NewReader(enc)) - if tt.useNumber { - dec.UseNumber() - } - if err := dec.Decode(vv.Interface()); err != nil { - t.Fatalf("%s: Decode(%#q) error after roundtrip: %v", tt.Where, enc, err) - } - if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { - t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", - tt.Where, v.Elem().Interface(), vv.Elem().Interface(), - stripWhitespace(string(enc)), stripWhitespace(string(in))) - } - } - }) - } -} - -func TestUnmarshalMarshal(t *testing.T) { - initBig() - var v any - if err := Unmarshal(jsonBig, &v); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - b, err := Marshal(v) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if !bytes.Equal(jsonBig, b) { - t.Errorf("Marshal:") - diff(t, b, jsonBig) - return - } -} - -// Independent of Decode, basic coverage of the accessors in Number -func TestNumberAccessors(t *testing.T) { - tests := []struct { - CaseName - in string - i int64 - intErr string - f float64 - floatErr string - }{ - {CaseName: Name(""), in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, - {CaseName: Name(""), in: "-12", i: -12, f: -12.0}, - {CaseName: Name(""), in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - n := Number(tt.in) - if got := n.String(); got != tt.in { - t.Errorf("%s: Number(%q).String() = %s, want %s", tt.Where, tt.in, got, tt.in) - } - if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { - t.Errorf("%s: Number(%q).Int64() = %d, want %d", tt.Where, tt.in, i, tt.i) - } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { - t.Errorf("%s: Number(%q).Int64() error:\n\tgot: %v\n\twant: %v", tt.Where, tt.in, err, tt.intErr) - } - if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { - t.Errorf("%s: Number(%q).Float64() = %g, want %g", tt.Where, tt.in, f, tt.f) - } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { - t.Errorf("%s: Number(%q).Float64() error:\n\tgot %v\n\twant: %v", tt.Where, tt.in, err, tt.floatErr) - } - }) - } -} - -func TestLargeByteSlice(t *testing.T) { - s0 := make([]byte, 2000) - for i := range s0 { - s0[i] = byte(i) - } - b, err := Marshal(s0) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var s1 []byte - if err := Unmarshal(b, &s1); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !bytes.Equal(s0, s1) { - t.Errorf("Marshal:") - diff(t, s0, s1) - } -} - -type Xint struct { - X int -} - -func TestUnmarshalInterface(t *testing.T) { - var xint Xint - var i any = &xint - if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if xint.X != 1 { - t.Fatalf("xint.X = %d, want 1", xint.X) - } -} - -func TestUnmarshalPtrPtr(t *testing.T) { - var xint Xint - pxint := &xint - if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if xint.X != 1 { - t.Fatalf("xint.X = %d, want 1", xint.X) - } -} - -func TestEscape(t *testing.T) { - const input = `"foobar"` + " [\u2028 \u2029]" - const want = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` - got, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Errorf("Marshal(%#q):\n\tgot: %s\n\twant: %s", input, got, want) - } -} - -// If people misuse the ,string modifier, the error message should be -// helpful, telling the user that they're doing it wrong. -func TestErrorMessageFromMisusedString(t *testing.T) { - // WrongString is a struct that's misusing the ,string modifier. - type WrongString struct { - Message string `json:"result,string"` - } - tests := []struct { - CaseName - in, err string - }{ - {Name(""), `{"result":"x"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character 'x' looking for beginning of object key string`}, - {Name(""), `{"result":"foo"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character 'f' looking for beginning of object key string`}, - {Name(""), `{"result":"123"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character '1' looking for beginning of object key string`}, - {Name(""), `{"result":123}`, `json: cannot unmarshal JSON number into WrongString.result of Go type string`}, - {Name(""), `{"result":"\""}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: unexpected end of JSON input`}, - {Name(""), `{"result":"\"foo"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: unexpected end of JSON input`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - r := strings.NewReader(tt.in) - var s WrongString - err := NewDecoder(r).Decode(&s) - got := fmt.Sprintf("%v", err) - if got != tt.err { - t.Errorf("%s: Decode error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.err) - } - }) - } -} - -type All struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Uintptr uintptr - Float32 float32 - Float64 float64 - - Foo string `json:"bar"` - Foo2 string `json:"bar2,dummyopt"` - - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - - PBool *bool - PInt *int - PInt8 *int8 - PInt16 *int16 - PInt32 *int32 - PInt64 *int64 - PUint *uint - PUint8 *uint8 - PUint16 *uint16 - PUint32 *uint32 - PUint64 *uint64 - PUintptr *uintptr - PFloat32 *float32 - PFloat64 *float64 - - String string - PString *string - - Map map[string]Small - MapP map[string]*Small - PMap *map[string]Small - PMapP *map[string]*Small - - EmptyMap map[string]Small - NilMap map[string]Small - - Slice []Small - SliceP []*Small - PSlice *[]Small - PSliceP *[]*Small - - EmptySlice []Small - NilSlice []Small - - StringSlice []string - ByteSlice []byte - - Small Small - PSmall *Small - PPSmall **Small - - Interface any - PInterface *any - - unexported int -} - -type Small struct { - Tag string -} - -var allValue = All{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Uintptr: 12, - Float32: 14.1, - Float64: 15.1, - Foo: "foo", - Foo2: "foo2", - IntStr: 42, - UintptrStr: 44, - String: "16", - Map: map[string]Small{ - "17": {Tag: "tag17"}, - "18": {Tag: "tag18"}, - }, - MapP: map[string]*Small{ - "19": {Tag: "tag19"}, - "20": nil, - }, - EmptyMap: map[string]Small{}, - Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, - SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, - EmptySlice: []Small{}, - StringSlice: []string{"str24", "str25", "str26"}, - ByteSlice: []byte{27, 28, 29}, - Small: Small{Tag: "tag30"}, - PSmall: &Small{Tag: "tag31"}, - Interface: 5.2, -} - -var pallValue = All{ - PBool: &allValue.Bool, - PInt: &allValue.Int, - PInt8: &allValue.Int8, - PInt16: &allValue.Int16, - PInt32: &allValue.Int32, - PInt64: &allValue.Int64, - PUint: &allValue.Uint, - PUint8: &allValue.Uint8, - PUint16: &allValue.Uint16, - PUint32: &allValue.Uint32, - PUint64: &allValue.Uint64, - PUintptr: &allValue.Uintptr, - PFloat32: &allValue.Float32, - PFloat64: &allValue.Float64, - PString: &allValue.String, - PMap: &allValue.Map, - PMapP: &allValue.MapP, - PSlice: &allValue.Slice, - PSliceP: &allValue.SliceP, - PPSmall: &allValue.PSmall, - PInterface: &allValue.Interface, -} - -var allValueIndent = `{ - "Bool": true, - "Int": 2, - "Int8": 3, - "Int16": 4, - "Int32": 5, - "Int64": 6, - "Uint": 7, - "Uint8": 8, - "Uint16": 9, - "Uint32": 10, - "Uint64": 11, - "Uintptr": 12, - "Float32": 14.1, - "Float64": 15.1, - "bar": "foo", - "bar2": "foo2", - "IntStr": "42", - "UintptrStr": "44", - "PBool": null, - "PInt": null, - "PInt8": null, - "PInt16": null, - "PInt32": null, - "PInt64": null, - "PUint": null, - "PUint8": null, - "PUint16": null, - "PUint32": null, - "PUint64": null, - "PUintptr": null, - "PFloat32": null, - "PFloat64": null, - "String": "16", - "PString": null, - "Map": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "MapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "PMap": null, - "PMapP": null, - "EmptyMap": {}, - "NilMap": null, - "Slice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "SliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "PSlice": null, - "PSliceP": null, - "EmptySlice": [], - "NilSlice": null, - "StringSlice": [ - "str24", - "str25", - "str26" - ], - "ByteSlice": "Gxwd", - "Small": { - "Tag": "tag30" - }, - "PSmall": { - "Tag": "tag31" - }, - "PPSmall": null, - "Interface": 5.2, - "PInterface": null -}` - -var allValueCompact = stripWhitespace(allValueIndent) - -var pallValueIndent = `{ - "Bool": false, - "Int": 0, - "Int8": 0, - "Int16": 0, - "Int32": 0, - "Int64": 0, - "Uint": 0, - "Uint8": 0, - "Uint16": 0, - "Uint32": 0, - "Uint64": 0, - "Uintptr": 0, - "Float32": 0, - "Float64": 0, - "bar": "", - "bar2": "", - "IntStr": "0", - "UintptrStr": "0", - "PBool": true, - "PInt": 2, - "PInt8": 3, - "PInt16": 4, - "PInt32": 5, - "PInt64": 6, - "PUint": 7, - "PUint8": 8, - "PUint16": 9, - "PUint32": 10, - "PUint64": 11, - "PUintptr": 12, - "PFloat32": 14.1, - "PFloat64": 15.1, - "String": "", - "PString": "16", - "Map": null, - "MapP": null, - "PMap": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "PMapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "EmptyMap": null, - "NilMap": null, - "Slice": null, - "SliceP": null, - "PSlice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "PSliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "EmptySlice": null, - "NilSlice": null, - "StringSlice": null, - "ByteSlice": null, - "Small": { - "Tag": "" - }, - "PSmall": null, - "PPSmall": { - "Tag": "tag31" - }, - "Interface": null, - "PInterface": 5.2 -}` - -var pallValueCompact = stripWhitespace(pallValueIndent) - -func TestRefUnmarshal(t *testing.T) { - type S struct { - // Ref is defined in encode_test.go. - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - } - want := S{ - R0: 12, - R1: new(Ref), - R2: 13, - R3: new(RefText), - } - *want.R1 = 12 - *want.R3 = 13 - - var got S - if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("Unmarsha:\n\tgot: %+v\n\twant: %+v", got, want) - } -} - -// Test that the empty string doesn't panic decoding when ,string is specified -// Issue 3450 -func TestEmptyString(t *testing.T) { - type T2 struct { - Number1 int `json:",string"` - Number2 int `json:",string"` - } - data := `{"Number1":"1", "Number2":""}` - dec := NewDecoder(strings.NewReader(data)) - var got T2 - switch err := dec.Decode(&got); { - case err == nil: - t.Fatalf("Decode error: got nil, want non-nil") - case got.Number1 != 1: - t.Fatalf("Decode: got.Number1 = %d, want 1", got.Number1) - } -} - -// Test that a null for ,string is not replaced with the previous quoted string (issue 7046). -// It should also not be an error (issue 2540, issue 8587). -func TestNullString(t *testing.T) { - type T struct { - A int `json:",string"` - B int `json:",string"` - C *int `json:",string"` - } - data := []byte(`{"A": "1", "B": null, "C": null}`) - var s T - s.B = 1 - s.C = new(int) - *s.C = 2 - switch err := Unmarshal(data, &s); { - case err != nil: - t.Fatalf("Unmarshal error: %v", err) - case s.B != 1: - t.Fatalf("Unmarshal: s.B = %d, want 1", s.B) - case s.C != nil: - t.Fatalf("Unmarshal: s.C = %d, want non-nil", s.C) - } -} - -func addr[T any](v T) *T { - return &v -} - -func TestInterfaceSet(t *testing.T) { - errUnmarshal := &UnmarshalTypeError{Value: "object", Offset: 5, Type: reflect.TypeFor[int](), Field: "X"} - tests := []struct { - CaseName - pre any - json string - post any - }{ - {Name(""), "foo", `"bar"`, "bar"}, - {Name(""), "foo", `2`, 2.0}, - {Name(""), "foo", `true`, true}, - {Name(""), "foo", `null`, nil}, - {Name(""), map[string]any{}, `true`, true}, - {Name(""), []string{}, `true`, true}, - - {Name(""), any(nil), `null`, any(nil)}, - {Name(""), (*int)(nil), `null`, any(nil)}, - {Name(""), (*int)(addr(0)), `null`, any(nil)}, - {Name(""), (*int)(addr(1)), `null`, any(nil)}, - {Name(""), (**int)(nil), `null`, any(nil)}, - {Name(""), (**int)(addr[*int](nil)), `null`, (**int)(addr[*int](nil))}, - {Name(""), (**int)(addr(addr(1))), `null`, (**int)(addr[*int](nil))}, - {Name(""), (***int)(nil), `null`, any(nil)}, - {Name(""), (***int)(addr[**int](nil)), `null`, (***int)(addr[**int](nil))}, - {Name(""), (***int)(addr(addr[*int](nil))), `null`, (***int)(addr[**int](nil))}, - {Name(""), (***int)(addr(addr(addr(1)))), `null`, (***int)(addr[**int](nil))}, - - {Name(""), any(nil), `2`, float64(2)}, - {Name(""), (int)(1), `2`, float64(2)}, - {Name(""), (*int)(nil), `2`, float64(2)}, - {Name(""), (*int)(addr(0)), `2`, (*int)(addr(2))}, - {Name(""), (*int)(addr(1)), `2`, (*int)(addr(2))}, - {Name(""), (**int)(nil), `2`, float64(2)}, - {Name(""), (**int)(addr[*int](nil)), `2`, (**int)(addr(addr(2)))}, - {Name(""), (**int)(addr(addr(1))), `2`, (**int)(addr(addr(2)))}, - {Name(""), (***int)(nil), `2`, float64(2)}, - {Name(""), (***int)(addr[**int](nil)), `2`, (***int)(addr(addr(addr(2))))}, - {Name(""), (***int)(addr(addr[*int](nil))), `2`, (***int)(addr(addr(addr(2))))}, - {Name(""), (***int)(addr(addr(addr(1)))), `2`, (***int)(addr(addr(addr(2))))}, - - {Name(""), any(nil), `{}`, map[string]any{}}, - {Name(""), (int)(1), `{}`, map[string]any{}}, - {Name(""), (*int)(nil), `{}`, map[string]any{}}, - {Name(""), (*int)(addr(0)), `{}`, errUnmarshal}, - {Name(""), (*int)(addr(1)), `{}`, errUnmarshal}, - {Name(""), (**int)(nil), `{}`, map[string]any{}}, - {Name(""), (**int)(addr[*int](nil)), `{}`, errUnmarshal}, - {Name(""), (**int)(addr(addr(1))), `{}`, errUnmarshal}, - {Name(""), (***int)(nil), `{}`, map[string]any{}}, - {Name(""), (***int)(addr[**int](nil)), `{}`, errUnmarshal}, - {Name(""), (***int)(addr(addr[*int](nil))), `{}`, errUnmarshal}, - {Name(""), (***int)(addr(addr(addr(1)))), `{}`, errUnmarshal}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b := struct{ X any }{tt.pre} - blob := `{"X":` + tt.json + `}` - if err := Unmarshal([]byte(blob), &b); err != nil { - if wantErr, _ := tt.post.(error); equalError(err, wantErr) { - return - } - t.Fatalf("%s: Unmarshal(%#q) error: %v", tt.Where, blob, err) - } - if !reflect.DeepEqual(b.X, tt.post) { - t.Errorf("%s: Unmarshal(%#q):\n\tpre.X: %#v\n\tgot.X: %#v\n\twant.X: %#v", tt.Where, blob, tt.pre, b.X, tt.post) - } - }) - } -} - -type NullTest struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Float32 float32 - Float64 float64 - String string - PBool *bool - Map map[string]string - Slice []string - Interface any - - PRaw *RawMessage - PTime *time.Time - PBigInt *big.Int - PText *MustNotUnmarshalText - PBuffer *bytes.Buffer // has methods, just not relevant ones - PStruct *struct{} - - Raw RawMessage - Time time.Time - BigInt big.Int - Text MustNotUnmarshalText - Buffer bytes.Buffer - Struct struct{} -} - -// JSON null values should be ignored for primitives and string values instead of resulting in an error. -// Issue 2540 -func TestUnmarshalNulls(t *testing.T) { - // Unmarshal docs: - // 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 - // on the value and produces no error. - - jsonData := []byte(`{ - "Bool" : null, - "Int" : null, - "Int8" : null, - "Int16" : null, - "Int32" : null, - "Int64" : null, - "Uint" : null, - "Uint8" : null, - "Uint16" : null, - "Uint32" : null, - "Uint64" : null, - "Float32" : null, - "Float64" : null, - "String" : null, - "PBool": null, - "Map": null, - "Slice": null, - "Interface": null, - "PRaw": null, - "PTime": null, - "PBigInt": null, - "PText": null, - "PBuffer": null, - "PStruct": null, - "Raw": null, - "Time": null, - "BigInt": null, - "Text": null, - "Buffer": null, - "Struct": null - }`) - nulls := NullTest{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Float32: 12.1, - Float64: 13.1, - String: "14", - PBool: new(bool), - Map: map[string]string{}, - Slice: []string{}, - Interface: new(MustNotUnmarshalJSON), - PRaw: new(RawMessage), - PTime: new(time.Time), - PBigInt: new(big.Int), - PText: new(MustNotUnmarshalText), - PStruct: new(struct{}), - PBuffer: new(bytes.Buffer), - Raw: RawMessage("123"), - Time: time.Unix(123456789, 0), - BigInt: *big.NewInt(123), - } - - before := nulls.Time.String() - - err := Unmarshal(jsonData, &nulls) - if err != nil { - t.Errorf("Unmarshal of null values failed: %v", err) - } - if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || - nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || - nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { - t.Errorf("Unmarshal of null values affected primitives") - } - - if nulls.PBool != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBool") - } - if nulls.Map != nil { - t.Errorf("Unmarshal of null did not clear nulls.Map") - } - if nulls.Slice != nil { - t.Errorf("Unmarshal of null did not clear nulls.Slice") - } - if nulls.Interface != nil { - t.Errorf("Unmarshal of null did not clear nulls.Interface") - } - if nulls.PRaw != nil { - t.Errorf("Unmarshal of null did not clear nulls.PRaw") - } - if nulls.PTime != nil { - t.Errorf("Unmarshal of null did not clear nulls.PTime") - } - if nulls.PBigInt != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBigInt") - } - if nulls.PText != nil { - t.Errorf("Unmarshal of null did not clear nulls.PText") - } - if nulls.PBuffer != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBuffer") - } - if nulls.PStruct != nil { - t.Errorf("Unmarshal of null did not clear nulls.PStruct") - } - - if string(nulls.Raw) != "null" { - t.Errorf("Unmarshal of RawMessage null did not record null: %v", string(nulls.Raw)) - } - if nulls.Time.String() != before { - t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) - } - if nulls.BigInt.String() != "123" { - t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) - } -} - -type MustNotUnmarshalJSON struct{} - -func (x MustNotUnmarshalJSON) UnmarshalJSON(data []byte) error { - return errors.New("MustNotUnmarshalJSON was used") -} - -type MustNotUnmarshalText struct{} - -func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { - return errors.New("MustNotUnmarshalText was used") -} - -func TestStringKind(t *testing.T) { - type stringKind string - want := map[stringKind]int{"foo": 42} - data, err := Marshal(want) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got map[stringKind]int - err = Unmarshal(data, &got) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !maps.Equal(got, want) { - t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) - } -} - -// Custom types with []byte as underlying type could not be marshaled -// and then unmarshaled. -// Issue 8962. -func TestByteKind(t *testing.T) { - type byteKind []byte - want := byteKind("hello") - data, err := Marshal(want) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got byteKind - err = Unmarshal(data, &got) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !slices.Equal(got, want) { - t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) - } -} - -// The fix for issue 8962 introduced a regression. -// Issue 12921. -func TestSliceOfCustomByte(t *testing.T) { - type Uint8 uint8 - want := []Uint8("hello") - data, err := Marshal(want) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got []Uint8 - err = Unmarshal(data, &got) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if !slices.Equal(got, want) { - t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) - } -} - -func TestUnmarshalTypeError(t *testing.T) { - tests := []struct { - CaseName - dest any - in string - }{ - {Name(""), new(string), `{"user": "name"}`}, // issue 4628. - {Name(""), new(error), `{}`}, // issue 4222 - {Name(""), new(error), `[]`}, - {Name(""), new(error), `""`}, - {Name(""), new(error), `123`}, - {Name(""), new(error), `true`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.in), tt.dest) - if _, ok := err.(*UnmarshalTypeError); !ok { - t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %T\n\twant: %T", - tt.Where, tt.in, tt.dest, err, new(UnmarshalTypeError)) - } - }) - } -} - -func TestUnmarshalSyntax(t *testing.T) { - var x any - tests := []struct { - CaseName - in string - }{ - {Name(""), "tru"}, - {Name(""), "fals"}, - {Name(""), "nul"}, - {Name(""), "123e"}, - {Name(""), `"hello`}, - {Name(""), `[1,2,3`}, - {Name(""), `{"key":1`}, - {Name(""), `{"key":1,`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.in), &x) - if _, ok := err.(*SyntaxError); !ok { - t.Errorf("%s: Unmarshal(%#q, any):\n\tgot: %T\n\twant: %T", - tt.Where, tt.in, err, new(SyntaxError)) - } - }) - } -} - -// Test handling of unexported fields that should be ignored. -// Issue 4660 -type unexportedFields struct { - Name string - m map[string]any `json:"-"` - m2 map[string]any `json:"abcd"` - - s []int `json:"-"` -} - -func TestUnmarshalUnexported(t *testing.T) { - input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` - want := &unexportedFields{Name: "Bob"} - - out := &unexportedFields{} - err := Unmarshal([]byte(input), out) - if err != nil { - t.Errorf("Unmarshal error: %v", err) - } - if !reflect.DeepEqual(out, want) { - t.Errorf("Unmarshal:\n\tgot: %+v\n\twant: %+v", out, want) - } -} - -// Time3339 is a time.Time which encodes to and from JSON -// as an RFC 3339 time in UTC. -type Time3339 time.Time - -func (t *Time3339) UnmarshalJSON(b []byte) error { - if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { - return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) - } - tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) - if err != nil { - return err - } - *t = Time3339(tm) - return nil -} - -func TestUnmarshalJSONLiteralError(t *testing.T) { - var t3 Time3339 - switch err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3); { - case err == nil: - t.Fatalf("Unmarshal error: got nil, want non-nil") - case !strings.Contains(err.Error(), "range"): - t.Errorf("Unmarshal error:\n\tgot: %v\n\twant: out of range", err) - } -} - -// Test that extra object elements in an array do not result in a -// "data changing underfoot" error. -// Issue 3717 -func TestSkipArrayObjects(t *testing.T) { - json := `[{}]` - var dest [0]any - - err := Unmarshal([]byte(json), &dest) - if err != nil { - t.Errorf("Unmarshal error: %v", err) - } -} - -// Test semantics of pre-filled data, such as struct fields, map elements, -// slices, and arrays. -// Issues 4900 and 8837, among others. -func TestPrefilled(t *testing.T) { - // Values here change, cannot reuse table across runs. - tests := []struct { - CaseName - in string - ptr any - out any - }{{ - CaseName: Name(""), - in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, - }, { - CaseName: Name(""), - in: `{"X": 1, "Y": 2}`, - 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}, - }, { - CaseName: Name(""), - in: `[2]`, - ptr: &[]int{1}, - out: &[]int{2}, - }, { - CaseName: Name(""), - in: `[2, 3]`, - ptr: &[]int{1}, - out: &[]int{2, 3}, - }, { - CaseName: Name(""), - in: `[2, 3]`, - ptr: &[...]int{1}, - out: &[...]int{2}, - }, { - CaseName: Name(""), - in: `[3]`, - ptr: &[...]int{1, 2}, - out: &[...]int{3, 0}, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - ptrstr := fmt.Sprintf("%v", tt.ptr) - err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here - if err != nil { - t.Errorf("%s: Unmarshal error: %v", tt.Where, err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %v\n\twant: %v", tt.Where, tt.in, ptrstr, tt.ptr, tt.out) - } - }) - } -} - -func TestInvalidUnmarshal(t *testing.T) { - tests := []struct { - CaseName - in string - v any - wantErr error - }{ - {Name(""), `{"a":"1"}`, nil, &InvalidUnmarshalError{}}, - {Name(""), `{"a":"1"}`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, - {Name(""), `{"a":"1"}`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, - {Name(""), `123`, nil, &InvalidUnmarshalError{}}, - {Name(""), `123`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, - {Name(""), `123`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, - {Name(""), `123`, new(net.IP), &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[net.IP](), Offset: len64(``), Err: errors.New("JSON value must be string type")}}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - switch gotErr := Unmarshal([]byte(tt.in), tt.v); { - case gotErr == nil: - t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) - case !reflect.DeepEqual(gotErr, tt.wantErr): - t.Errorf("%s: Unmarshal error:\n\tgot: %#v\n\twant: %#v", tt.Where, gotErr, tt.wantErr) - } - }) - } -} - -// Test that string option is ignored for invalid types. -// Issue 9812. -func TestInvalidStringOption(t *testing.T) { - num := 0 - item := struct { - T time.Time `json:",string"` - M map[string]string `json:",string"` - S []string `json:",string"` - A [1]string `json:",string"` - I any `json:",string"` - P *int `json:",string"` - }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} - - data, err := Marshal(item) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - - err = Unmarshal(data, &item) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } -} - -// Test unmarshal behavior with regards to embedded unexported structs. -// -// (Issue 21357) If the embedded struct is a pointer and is unallocated, -// this returns an error because unmarshal cannot set the field. -// -// (Issue 24152) If the embedded struct is given an explicit name, -// ensure that the normal unmarshal logic does not panic in reflect. -// -// (Issue 28145) If the embedded struct is given an explicit name and has -// exported methods, don't cause a panic trying to get its value. -func TestUnmarshalEmbeddedUnexported(t *testing.T) { - type ( - embed1 struct{ Q int } - embed2 struct{ Q int } - embed3 struct { - Q int64 `json:",string"` - } - S1 struct { - *embed1 - R int - } - S2 struct { - *embed1 - Q int - } - S3 struct { - embed1 - R int - } - S4 struct { - *embed1 - embed2 - } - S5 struct { - *embed3 - R int - } - S6 struct { - embed1 `json:"embed1"` - } - S7 struct { - embed1 `json:"embed1"` - embed2 - } - S8 struct { - embed1 `json:"embed1"` - embed2 `json:"embed2"` - Q int - } - S9 struct { - unexportedWithMethods `json:"embed"` - } - ) - - tests := []struct { - CaseName - in string - ptr any - out any - err error - }{{ - // Error since we cannot set S1.embed1, but still able to set S1.R. - CaseName: Name(""), - in: `{"R":2,"Q":1}`, - ptr: new(S1), - out: &S1{R: 2}, - err: &UnmarshalTypeError{ - Type: reflect.TypeFor[S1](), - Offset: len64(`{"R":2,"Q":`), - Struct: "S1", - Field: "Q", - Err: errors.New("cannot set embedded pointer to unexported struct type"), - }, - }, { - // The top level Q field takes precedence. - CaseName: Name(""), - in: `{"Q":1}`, - ptr: new(S2), - out: &S2{Q: 1}, - }, { - // No issue with non-pointer variant. - CaseName: Name(""), - in: `{"R":2,"Q":1}`, - ptr: new(S3), - out: &S3{embed1: embed1{Q: 1}, R: 2}, - }, { - // No error since both embedded structs have field R, which annihilate each other. - // Thus, no attempt is made at setting S4.embed1. - CaseName: Name(""), - in: `{"R":2}`, - ptr: new(S4), - out: new(S4), - }, { - // Error since we cannot set S5.embed1, but still able to set S5.R. - CaseName: Name(""), - in: `{"R":2,"Q":1}`, - ptr: new(S5), - out: &S5{R: 2}, - err: &UnmarshalTypeError{ - Type: reflect.TypeFor[S5](), - Offset: len64(`{"R":2,"Q":`), - Struct: "S5", - Field: "Q", - Err: errors.New("cannot set embedded pointer to unexported struct type"), - }, - }, { - // Issue 24152, ensure decodeState.indirect does not panic. - CaseName: Name(""), - in: `{"embed1": {"Q": 1}}`, - ptr: new(S6), - out: &S6{embed1{1}}, - }, { - // Issue 24153, check that we can still set forwarded fields even in - // the presence of a name conflict. - // - // This relies on obscure behavior of reflect where it is possible - // to set a forwarded exported field on an unexported embedded struct - // even though there is a name conflict, even when it would have been - // impossible to do so according to Go visibility rules. - // Go forbids this because it is ambiguous whether S7.Q refers to - // S7.embed1.Q or S7.embed2.Q. Since embed1 and embed2 are unexported, - // it should be impossible for an external package to set either Q. - // - // It is probably okay for a future reflect change to break this. - CaseName: Name(""), - in: `{"embed1": {"Q": 1}, "Q": 2}`, - ptr: new(S7), - out: &S7{embed1{1}, embed2{2}}, - }, { - // Issue 24153, similar to the S7 case. - CaseName: Name(""), - in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, - ptr: new(S8), - out: &S8{embed1{1}, embed2{2}, 3}, - }, { - // Issue 228145, similar to the cases above. - CaseName: Name(""), - in: `{"embed": {}}`, - ptr: new(S9), - out: &S9{}, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.in), tt.ptr) - if !equalError(err, tt.err) { - t.Errorf("%s: Unmarshal error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("%s: Unmarshal:\n\tgot: %#+v\n\twant: %#+v", tt.Where, tt.ptr, tt.out) - } - }) - } -} - -func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { - tests := []struct { - CaseName - in string - err error - }{{ - CaseName: Name(""), - in: `1 false null :`, - err: &SyntaxError{"invalid character ':' looking for beginning of value", len64(`1 false null `)}, - }, { - CaseName: Name(""), - in: `1 [] [,]`, - err: &SyntaxError{"invalid character ',' looking for beginning of value", len64(`1 [] [`)}, - }, { - CaseName: Name(""), - in: `1 [] [true:]`, - err: &SyntaxError{"invalid character ':' after array element", len64(`1 [] [true`)}, - }, { - CaseName: Name(""), - in: `1 {} {"x"=}`, - err: &SyntaxError{"invalid character '=' after object key", len64(`1 {} {"x"`)}, - }, { - CaseName: Name(""), - in: `falsetruenul#`, - err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", len64(`falsetruenul`)}, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - dec := NewDecoder(strings.NewReader(tt.in)) - var err error - for err == nil { - var v any - err = dec.Decode(&v) - } - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("%s: Decode error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) - } - }) - } -} - -type unmarshalPanic struct{} - -func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) } - -func TestUnmarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - Unmarshal([]byte("{}"), &unmarshalPanic{}) - t.Fatalf("Unmarshal should have panicked") -} - -type textUnmarshalerString string - -func (m *textUnmarshalerString) UnmarshalText(text []byte) error { - *m = textUnmarshalerString(strings.ToLower(string(text))) - return nil -} - -// Test unmarshal to a map, where the map key is a user defined type. -// See golang.org/issues/34437. -func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - - if _, ok := p["foo"]; !ok { - t.Errorf(`key "foo" missing in map: %v`, p) - } -} - -func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { - // See golang.org/issues/38105. - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if _, ok := p["开源"]; !ok { - t.Errorf(`key "开源" missing in map: %v`, p) - } - - // See golang.org/issues/38126. - type T struct { - F1 string `json:"F1,string"` - } - wantT := T{"aaa\tbbb"} - - b, err := Marshal(wantT) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var gotT T - if err := Unmarshal(b, &gotT); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if gotT != wantT { - t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) - } - - // See golang.org/issues/39555. - input := map[textUnmarshalerString]string{"FOO": "", `"`: ""} - - encoded, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - var got map[textUnmarshalerString]string - if err := Unmarshal(encoded, &got); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - want := map[textUnmarshalerString]string{"foo": "", `"`: ""} - if !maps.Equal(got, want) { - t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) - } -} - -func TestUnmarshalMaxDepth(t *testing.T) { - tests := []struct { - CaseName - data string - errMaxDepth bool - }{{ - CaseName: Name("ArrayUnderMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, - errMaxDepth: false, - }, { - CaseName: Name("ArrayOverMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, - errMaxDepth: true, - }, { - CaseName: Name("ArrayOverStackDepth"), - data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, - errMaxDepth: true, - }, { - CaseName: Name("ObjectUnderMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, - errMaxDepth: false, - }, { - CaseName: Name("ObjectOverMaxNestingDepth"), - data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, - errMaxDepth: true, - }, { - CaseName: Name("ObjectOverStackDepth"), - data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, - errMaxDepth: true, - }} - - targets := []struct { - CaseName - newValue func() any - }{{ - CaseName: Name("unstructured"), - newValue: func() any { - var v any - return &v - }, - }, { - CaseName: Name("typed named field"), - newValue: func() any { - v := struct { - A any `json:"a"` - }{} - return &v - }, - }, { - CaseName: Name("typed missing field"), - newValue: func() any { - v := struct { - B any `json:"b"` - }{} - return &v - }, - }, { - CaseName: Name("custom unmarshaler"), - newValue: func() any { - v := unmarshaler{} - return &v - }, - }} - - for _, tt := range tests { - for _, target := range targets { - t.Run(target.Name+"-"+tt.Name, func(t *testing.T) { - err := Unmarshal([]byte(tt.data), target.newValue()) - if !tt.errMaxDepth { - if err != nil { - t.Errorf("%s: %s: Unmarshal error: %v", tt.Where, target.Where, err) - } - } else { - if err == nil || !strings.Contains(err.Error(), "exceeded max depth") { - t.Errorf("%s: %s: Unmarshal error:\n\tgot: %v\n\twant: exceeded max depth", tt.Where, target.Where, err) - } - } - }) - } - } -} diff --git a/pkg/encoders/json/v2_diff_test.go b/pkg/encoders/json/v2_diff_test.go deleted file mode 100644 index 9d0798e..0000000 --- a/pkg/encoders/json/v2_diff_test.go +++ /dev/null @@ -1,1130 +0,0 @@ -// Copyright 2020 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. - -//go:build goexperiment.jsonv2 - -package json_test - -import ( - "errors" - "path" - "reflect" - "strings" - "testing" - "time" - - jsonv1 "encoding/json" - "encoding/json/jsontext" - jsonv2 "encoding/json/v2" -) - -// NOTE: This file serves as a list of semantic differences between v1 and v2. -// Each test explains how v1 behaves, how v2 behaves, and -// a rationale for why the behavior was changed. - -var jsonPackages = []struct { - Version string - Marshal func(any) ([]byte, error) - Unmarshal func([]byte, any) error -}{ - {"v1", jsonv1.Marshal, jsonv1.Unmarshal}, - {"v2", - func(in any) ([]byte, error) { return jsonv2.Marshal(in) }, - func(in []byte, out any) error { return jsonv2.Unmarshal(in, out) }}, -} - -// In v1, unmarshal matches struct fields using a case-insensitive match. -// In v2, unmarshal matches struct fields using a case-sensitive match. -// -// Case-insensitive matching is a surprising default and -// incurs significant performance cost when unmarshaling unknown fields. -// In v2, we can opt into v1-like behavior with the `case:ignore` tag option. -// The case-insensitive matching performed by v2 is looser than that of v1 -// where it also ignores dashes and underscores. -// This allows v2 to match fields regardless of whether the name is in -// snake_case, camelCase, or kebab-case. -// -// Related issue: -// -// https://go.dev/issue/14750 -func TestCaseSensitivity(t *testing.T) { - type Fields struct { - FieldA bool - FieldB bool `json:"fooBar"` - FieldC bool `json:"fizzBuzz,case:ignore"` // `case:ignore` is used by v2 to explicitly enable case-insensitive matching - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - // This is a mapping from Go field names to JSON member names to - // whether the JSON member name would match the Go field name. - type goName = string - type jsonName = string - onlyV1 := json.Version == "v1" - onlyV2 := json.Version == "v2" - allMatches := map[goName]map[jsonName]bool{ - "FieldA": { - "FieldA": true, // exact match - "fielda": onlyV1, // v1 is case-insensitive by default - "fieldA": onlyV1, // v1 is case-insensitive by default - "FIELDA": onlyV1, // v1 is case-insensitive by default - "FieldB": false, - "FieldC": false, - }, - "FieldB": { - "fooBar": true, // exact match for explicitly specified JSON name - "FooBar": onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided - "foobar": onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided - "FOOBAR": onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided - "fizzBuzz": false, - "FieldA": false, - "FieldB": false, // explicit JSON name means that the Go field name is not used for matching - "FieldC": false, - }, - "FieldC": { - "fizzBuzz": true, // exact match for explicitly specified JSON name - "fizzbuzz": true, // v2 is case-insensitive due to `case:ignore` tag - "FIZZBUZZ": true, // v2 is case-insensitive due to `case:ignore` tag - "fizz_buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores - "fizz-buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores - "fooBar": false, - "FieldA": false, - "FieldC": false, // explicit JSON name means that the Go field name is not used for matching - "FieldB": false, - }, - } - - for goFieldName, matches := range allMatches { - for jsonMemberName, wantMatch := range matches { - in := `{"` + jsonMemberName + `":true}` - var s Fields - if err := json.Unmarshal([]byte(in), &s); err != nil { - t.Fatalf("json.Unmarshal error: %v", err) - } - gotMatch := reflect.ValueOf(s).FieldByName(goFieldName).Bool() - if gotMatch != wantMatch { - t.Fatalf("%T.%s = %v, want %v", s, goFieldName, gotMatch, wantMatch) - } - } - } - }) - } -} - -// In v1, the "omitempty" option specifies that a struct field is omitted -// when marshaling if it is an empty Go value, which is defined as -// false, 0, a nil pointer, a nil interface value, and -// any empty array, slice, map, or string. -// -// In v2, the "omitempty" option specifies that a struct field is omitted -// when marshaling if it is an empty JSON value, which is defined as -// a JSON null or empty JSON string, object, or array. -// -// In v2, we also provide the "omitzero" option which specifies that a field -// is omitted if it is the zero Go value or if it implements an "IsZero() bool" -// method that reports true. Together, "omitzero" and "omitempty" can cover -// all the prior use cases of the v1 definition of "omitempty". -// Note that "omitempty" is defined in terms of the Go type system in v1, -// but now defined in terms of the JSON type system in v2. -// -// Related issues: -// -// https://go.dev/issue/11939 -// https://go.dev/issue/22480 -// https://go.dev/issue/29310 -// https://go.dev/issue/32675 -// https://go.dev/issue/45669 -// https://go.dev/issue/45787 -// https://go.dev/issue/50480 -// https://go.dev/issue/52803 -func TestOmitEmptyOption(t *testing.T) { - type Struct struct { - Foo string `json:",omitempty"` - Bar []int `json:",omitempty"` - Baz *Struct `json:",omitempty"` - } - type Types struct { - Bool bool `json:",omitempty"` - StringA string `json:",omitempty"` - StringB string `json:",omitempty"` - BytesA []byte `json:",omitempty"` - BytesB []byte `json:",omitempty"` - BytesC []byte `json:",omitempty"` - Int int `json:",omitempty"` - MapA map[string]string `json:",omitempty"` - MapB map[string]string `json:",omitempty"` - MapC map[string]string `json:",omitempty"` - StructA Struct `json:",omitempty"` - StructB Struct `json:",omitempty"` - StructC Struct `json:",omitempty"` - SliceA []string `json:",omitempty"` - SliceB []string `json:",omitempty"` - SliceC []string `json:",omitempty"` - Array [1]string `json:",omitempty"` - PointerA *string `json:",omitempty"` - PointerB *string `json:",omitempty"` - PointerC *string `json:",omitempty"` - InterfaceA any `json:",omitempty"` - InterfaceB any `json:",omitempty"` - InterfaceC any `json:",omitempty"` - InterfaceD any `json:",omitempty"` - } - - something := "something" - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - in := Types{ - Bool: false, - StringA: "", - StringB: something, - BytesA: nil, - BytesB: []byte{}, - BytesC: []byte(something), - Int: 0, - MapA: nil, - MapB: map[string]string{}, - MapC: map[string]string{something: something}, - StructA: Struct{}, - StructB: Struct{Bar: []int{}, Baz: new(Struct)}, - StructC: Struct{Foo: something}, - SliceA: nil, - SliceB: []string{}, - SliceC: []string{something}, - Array: [1]string{something}, - PointerA: nil, - PointerB: new(string), - PointerC: &something, - InterfaceA: nil, - InterfaceB: (*string)(nil), - InterfaceC: new(string), - InterfaceD: &something, - } - b, err := json.Marshal(in) - if err != nil { - t.Fatalf("json.Marshal error: %v", err) - } - var out map[string]any - if err := json.Unmarshal(b, &out); err != nil { - t.Fatalf("json.Unmarshal error: %v", err) - } - - onlyV1 := json.Version == "v1" - onlyV2 := json.Version == "v2" - wantPresent := map[string]bool{ - "Bool": onlyV2, // false is an empty Go bool, but is NOT an empty JSON value - "StringA": false, - "StringB": true, - "BytesA": false, - "BytesB": false, - "BytesC": true, - "Int": onlyV2, // 0 is an empty Go integer, but NOT an empty JSON value - "MapA": false, - "MapB": false, - "MapC": true, - "StructA": onlyV1, // Struct{} is NOT an empty Go value, but {} is an empty JSON value - "StructB": onlyV1, // Struct{...} is NOT an empty Go value, but {} is an empty JSON value - "StructC": true, - "SliceA": false, - "SliceB": false, - "SliceC": true, - "Array": true, - "PointerA": false, - "PointerB": onlyV1, // new(string) is NOT a nil Go pointer, but "" is an empty JSON value - "PointerC": true, - "InterfaceA": false, - "InterfaceB": onlyV1, // (*string)(nil) is NOT a nil Go interface, but null is an empty JSON value - "InterfaceC": onlyV1, // new(string) is NOT a nil Go interface, but "" is an empty JSON value - "InterfaceD": true, - } - for field, want := range wantPresent { - _, got := out[field] - if got != want { - t.Fatalf("%T.%s = %v, want %v", in, field, got, want) - } - } - }) - } -} - -func addr[T any](v T) *T { - return &v -} - -// In v1, the "string" option specifies that Go strings, bools, and numeric -// values are encoded within a JSON string when marshaling and -// are unmarshaled from its native representation escaped within a JSON string. -// The "string" option is not applied recursively, and so does not affect -// strings, bools, and numeric values within a Go slice or map, but -// does have special handling to affect the underlying value within a pointer. -// When unmarshaling, the "string" option permits decoding from a JSON null -// escaped within a JSON string in some inconsistent cases. -// -// In v2, the "string" option specifies that only numeric values are encoded as -// a JSON number within a JSON string when marshaling and are unmarshaled -// from either a JSON number or a JSON string containing a JSON number. -// The "string" option is applied recursively to all numeric sub-values, -// and thus affects numeric values within a Go slice or map. -// There is no support for escaped JSON nulls within a JSON string. -// -// The main utility for stringifying JSON numbers is because JSON parsers -// often represents numbers as IEEE 754 floating-point numbers. -// This results in a loss of precision representing 64-bit integer values. -// Consequently, many JSON-based APIs actually requires that such values -// be encoded within a JSON string. Since the main utility of stringification -// is for numeric values, v2 limits the effect of the "string" option -// to just numeric Go types. According to all code known by the Go module proxy, -// there are close to zero usages of the "string" option on a Go string or bool. -// -// Regarding the recursive application of the "string" option, -// there have been a number of issues filed about users being surprised that -// the "string" option does not recursively affect numeric values -// within a composite type like a Go map, slice, or interface value. -// In v1, specifying the "string" option on composite type has no effect -// and so this would be a largely backwards compatible change. -// -// The ability to decode from a JSON null wrapped within a JSON string -// is removed in v2 because this behavior was surprising and inconsistent in v1. -// -// Related issues: -// -// https://go.dev/issue/15624 -// https://go.dev/issue/20651 -// https://go.dev/issue/22177 -// https://go.dev/issue/32055 -// https://go.dev/issue/32117 -// https://go.dev/issue/50997 -func TestStringOption(t *testing.T) { - type Types struct { - String string `json:",string"` - Bool bool `json:",string"` - Int int `json:",string"` - Float float64 `json:",string"` - Map map[string]int `json:",string"` - Struct struct{ Field int } `json:",string"` - Slice []int `json:",string"` - Array [1]int `json:",string"` - PointerA *int `json:",string"` - PointerB *int `json:",string"` - PointerC **int `json:",string"` - InterfaceA any `json:",string"` - InterfaceB any `json:",string"` - } - - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - in := Types{ - String: "string", - Bool: true, - Int: 1, - Float: 1, - Map: map[string]int{"Name": 1}, - Struct: struct{ Field int }{1}, - Slice: []int{1}, - Array: [1]int{1}, - PointerA: nil, - PointerB: addr(1), - PointerC: addr(addr(1)), - InterfaceA: nil, - InterfaceB: 1, - } - quote := func(s string) string { - b, _ := jsontext.AppendQuote(nil, s) - return string(b) - } - quoteOnlyV1 := func(s string) string { - if json.Version == "v1" { - s = quote(s) - } - return s - } - quoteOnlyV2 := func(s string) string { - if json.Version == "v2" { - s = quote(s) - } - return s - } - want := strings.Join([]string{ - `{`, - `"String":` + quoteOnlyV1(`"string"`) + `,`, // in v1, Go strings are also stringified - `"Bool":` + quoteOnlyV1("true") + `,`, // in v1, Go bools are also stringified - `"Int":` + quote("1") + `,`, - `"Float":` + quote("1") + `,`, - `"Map":{"Name":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified - `"Struct":{"Field":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified - `"Slice":[` + quoteOnlyV2("1") + `],`, // in v2, numbers are recursively stringified - `"Array":[` + quoteOnlyV2("1") + `],`, // in v2, numbers are recursively stringified - `"PointerA":null,`, - `"PointerB":` + quote("1") + `,`, // in v1, numbers are stringified after a single pointer indirection - `"PointerC":` + quoteOnlyV2("1") + `,`, // in v2, numbers are recursively stringified - `"InterfaceA":null,`, - `"InterfaceB":` + quoteOnlyV2("1") + ``, // in v2, numbers are recursively stringified - `}`}, "") - got, err := json.Marshal(in) - if err != nil { - t.Fatalf("json.Marshal error: %v", err) - } - if string(got) != want { - t.Fatalf("json.Marshal = %s, want %s", got, want) - } - }) - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal/Null", json.Version), func(t *testing.T) { - var got Types - err := json.Unmarshal([]byte(`{ - "Bool": "null", - "Int": "null", - "PointerA": "null" - }`), &got) - switch { - case !reflect.DeepEqual(got, Types{}): - t.Fatalf("json.Unmarshal = %v, want %v", got, Types{}) - case json.Version == "v1" && err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case json.Version == "v2" && err == nil: - t.Fatal("json.Unmarshal error is nil, want non-nil") - } - }) - - t.Run(path.Join("Unmarshal/Bool", json.Version), func(t *testing.T) { - var got Types - want := map[string]Types{ - "v1": {Bool: true}, - "v2": {Bool: false}, - }[json.Version] - err := json.Unmarshal([]byte(`{"Bool": "true"}`), &got) - switch { - case !reflect.DeepEqual(got, want): - t.Fatalf("json.Unmarshal = %v, want %v", got, want) - case json.Version == "v1" && err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case json.Version == "v2" && err == nil: - t.Fatal("json.Unmarshal error is nil, want non-nil") - } - }) - - t.Run(path.Join("Unmarshal/Shallow", json.Version), func(t *testing.T) { - var got Types - want := Types{Int: 1, PointerB: addr(1)} - err := json.Unmarshal([]byte(`{ - "Int": "1", - "PointerB": "1" - }`), &got) - switch { - case !reflect.DeepEqual(got, want): - t.Fatalf("json.Unmarshal = %v, want %v", got, want) - case err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - } - }) - - t.Run(path.Join("Unmarshal/Deep", json.Version), func(t *testing.T) { - var got Types - want := map[string]Types{ - "v1": { - Map: map[string]int{"Name": 0}, - Slice: []int{0}, - PointerC: addr(addr(0)), - }, - "v2": { - Map: map[string]int{"Name": 1}, - Struct: struct{ Field int }{1}, - Slice: []int{1}, - Array: [1]int{1}, - PointerC: addr(addr(1)), - }, - }[json.Version] - err := json.Unmarshal([]byte(`{ - "Map": {"Name":"1"}, - "Struct": {"Field":"1"}, - "Slice": ["1"], - "Array": ["1"], - "PointerC": "1" - }`), &got) - switch { - case !reflect.DeepEqual(got, want): - t.Fatalf("json.Unmarshal =\n%v, want\n%v", got, want) - case json.Version == "v1" && err == nil: - t.Fatal("json.Unmarshal error is nil, want non-nil") - case json.Version == "v2" && err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - } - }) - } -} - -// In v1, nil slices and maps are marshaled as a JSON null. -// In v2, nil slices and maps are marshaled as an empty JSON object or array. -// -// Users of v2 can opt into the v1 behavior by setting -// the "format:emitnull" option in the `json` struct field tag: -// -// struct { -// S []string `json:",format:emitnull"` -// M map[string]string `json:",format:emitnull"` -// } -// -// JSON is a language-agnostic data interchange format. -// The fact that maps and slices are nil-able in Go is a semantic detail of the -// Go language. We should avoid leaking such details to the JSON representation. -// When JSON implementations leak language-specific details, -// it complicates transition to/from languages with different type systems. -// -// Furthermore, consider two related Go types: string and []byte. -// It's an asymmetric oddity of v1 that zero values of string and []byte marshal -// as an empty JSON string for the former, while the latter as a JSON null. -// The non-zero values of those types always marshal as JSON strings. -// -// Related issues: -// -// https://go.dev/issue/27589 -// https://go.dev/issue/37711 -func TestNilSlicesAndMaps(t *testing.T) { - type Composites struct { - B []byte // always encoded in v2 as a JSON string - S []string // always encoded in v2 as a JSON array - M map[string]string // always encoded in v2 as a JSON object - } - - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - in := []Composites{ - {B: []byte(nil), S: []string(nil), M: map[string]string(nil)}, - {B: []byte{}, S: []string{}, M: map[string]string{}}, - } - want := map[string]string{ - "v1": `[{"B":null,"S":null,"M":null},{"B":"","S":[],"M":{}}]`, - "v2": `[{"B":"","S":[],"M":{}},{"B":"","S":[],"M":{}}]`, // v2 emits nil slices and maps as empty JSON objects and arrays - }[json.Version] - got, err := json.Marshal(in) - if err != nil { - t.Fatalf("json.Marshal error: %v", err) - } - if string(got) != want { - t.Fatalf("json.Marshal = %s, want %s", got, want) - } - }) - } -} - -// In v1, unmarshaling into a Go array permits JSON arrays with any length. -// In v2, unmarshaling into a Go array requires that the JSON array -// have the exact same number of elements as the Go array. -// -// Go arrays are often used because the exact length has significant meaning. -// Ignoring this detail seems like a mistake. Also, the v1 behavior leads to -// silent data loss when excess JSON array elements are discarded. -func TestArrays(t *testing.T) { - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal/TooFew", json.Version), func(t *testing.T) { - var got [2]int - err := json.Unmarshal([]byte(`[1]`), &got) - switch { - case got != [2]int{1, 0}: - t.Fatalf(`json.Unmarshal = %v, want [1 0]`, got) - case json.Version == "v1" && err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case json.Version == "v2" && err == nil: - t.Fatal("json.Unmarshal error is nil, want non-nil") - } - }) - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal/TooMany", json.Version), func(t *testing.T) { - var got [2]int - err := json.Unmarshal([]byte(`[1,2,3]`), &got) - switch { - case got != [2]int{1, 2}: - t.Fatalf(`json.Unmarshal = %v, want [1 2]`, got) - case json.Version == "v1" && err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case json.Version == "v2" && err == nil: - t.Fatal("json.Unmarshal error is nil, want non-nil") - } - }) - } -} - -// In v1, byte arrays are treated as arrays of unsigned integers. -// In v2, byte arrays are treated as binary values (similar to []byte). -// This is to make the behavior of [N]byte and []byte more consistent. -// -// Users of v2 can opt into the v1 behavior by setting -// the "format:array" option in the `json` struct field tag: -// -// struct { -// B [32]byte `json:",format:array"` -// } -func TestByteArrays(t *testing.T) { - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - in := [4]byte{1, 2, 3, 4} - got, err := json.Marshal(in) - if err != nil { - t.Fatalf("json.Marshal error: %v", err) - } - want := map[string]string{ - "v1": `[1,2,3,4]`, - "v2": `"AQIDBA=="`, - }[json.Version] - if string(got) != want { - t.Fatalf("json.Marshal = %s, want %s", got, want) - } - }) - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - in := map[string]string{ - "v1": `[1,2,3,4]`, - "v2": `"AQIDBA=="`, - }[json.Version] - var got [4]byte - err := json.Unmarshal([]byte(in), &got) - switch { - case err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case got != [4]byte{1, 2, 3, 4}: - t.Fatalf("json.Unmarshal = %v, want [1 2 3 4]", got) - } - }) - } -} - -// CallCheck implements json.{Marshaler,Unmarshaler} on a pointer receiver. -type CallCheck string - -// MarshalJSON always returns a JSON string with the literal "CALLED". -func (*CallCheck) MarshalJSON() ([]byte, error) { - return []byte(`"CALLED"`), nil -} - -// UnmarshalJSON always stores a string with the literal "CALLED". -func (v *CallCheck) UnmarshalJSON([]byte) error { - *v = `CALLED` - return nil -} - -// In v1, the implementation is inconsistent about whether it calls -// MarshalJSON and UnmarshalJSON methods declared on pointer receivers -// when it has an unaddressable value (per reflect.Value.CanAddr) on hand. -// When marshaling, it never boxes the value on the heap to make it addressable, -// while it sometimes boxes values (e.g., for map entries) when unmarshaling. -// -// In v2, the implementation always calls MarshalJSON and UnmarshalJSON methods -// by boxing the value on the heap if necessary. -// -// The v1 behavior is surprising at best and buggy at worst. -// Unfortunately, it cannot be changed without breaking existing usages. -// -// Related issues: -// -// https://go.dev/issue/27722 -// https://go.dev/issue/33993 -// https://go.dev/issue/42508 -func TestPointerReceiver(t *testing.T) { - type Values struct { - S []CallCheck - A [1]CallCheck - M map[string]CallCheck - V CallCheck - I any - } - - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - var cc CallCheck - in := Values{ - S: []CallCheck{cc}, - A: [1]CallCheck{cc}, // MarshalJSON not called on v1 - M: map[string]CallCheck{"": cc}, // MarshalJSON not called on v1 - V: cc, // MarshalJSON not called on v1 - I: cc, // MarshalJSON not called on v1 - } - want := map[string]string{ - "v1": `{"S":["CALLED"],"A":[""],"M":{"":""},"V":"","I":""}`, - "v2": `{"S":["CALLED"],"A":["CALLED"],"M":{"":"CALLED"},"V":"CALLED","I":"CALLED"}`, - }[json.Version] - got, err := json.Marshal(in) - if err != nil { - t.Fatalf("json.Marshal error: %v", err) - } - if string(got) != want { - t.Fatalf("json.Marshal = %s, want %s", got, want) - } - }) - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - in := `{"S":[""],"A":[""],"M":{"":""},"V":"","I":""}` - called := CallCheck("CALLED") // resulting state if UnmarshalJSON is called - want := map[string]Values{ - "v1": { - S: []CallCheck{called}, - A: [1]CallCheck{called}, - M: map[string]CallCheck{"": called}, - V: called, - I: "", // UnmarshalJSON not called on v1; replaced with Go string - }, - "v2": { - S: []CallCheck{called}, - A: [1]CallCheck{called}, - M: map[string]CallCheck{"": called}, - V: called, - I: called, - }, - }[json.Version] - got := Values{ - A: [1]CallCheck{CallCheck("")}, - S: []CallCheck{CallCheck("")}, - M: map[string]CallCheck{"": CallCheck("")}, - V: CallCheck(""), - I: CallCheck(""), - } - if err := json.Unmarshal([]byte(in), &got); err != nil { - t.Fatalf("json.Unmarshal error: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Fatalf("json.Unmarshal = %v, want %v", got, want) - } - }) - } -} - -// In v1, maps are marshaled in a deterministic order. -// In v2, maps are marshaled in a non-deterministic order. -// -// The reason for the change is that v2 prioritizes performance and -// the guarantee that marshaling operates primarily in a streaming manner. -// -// The v2 API provides jsontext.Value.Canonicalize if stability is needed: -// -// (*jsontext.Value)(&b).Canonicalize() -// -// Related issue: -// -// https://go.dev/issue/7872 -// https://go.dev/issue/33714 -func TestMapDeterminism(t *testing.T) { - const iterations = 10 - in := map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9} - - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - outs := make(map[string]bool) - for range iterations { - b, err := json.Marshal(in) - if err != nil { - t.Fatalf("json.Marshal error: %v", err) - } - outs[string(b)] = true - } - switch { - case json.Version == "v1" && len(outs) != 1: - t.Fatalf("json.Marshal encoded to %d unique forms, expected 1", len(outs)) - case json.Version == "v2" && len(outs) == 1: - t.Logf("json.Marshal encoded to 1 unique form by chance; are you feeling lucky?") - } - }) - } -} - -// In v1, JSON string encoding escapes special characters related to HTML. -// In v2, JSON string encoding uses a normalized representation (per RFC 8785). -// -// Users of v2 can opt into the v1 behavior by setting EscapeForHTML and EscapeForJS. -// -// Escaping HTML-specific characters in a JSON library is a layering violation. -// It presumes that JSON is always used with HTML and ignores other -// similar classes of injection attacks (e.g., SQL injection). -// Users of JSON with HTML should either manually ensure that embedded JSON is -// properly escaped or be relying on a module like "github.com/google/safehtml" -// to handle safe interoperability of JSON and HTML. -func TestEscapeHTML(t *testing.T) { - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - const in = `` - got, err := json.Marshal(in) - if err != nil { - t.Fatalf("json.Marshal error: %v", err) - } - want := map[string]string{ - "v1": `"\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e"`, - "v2": `""`, - }[json.Version] - if string(got) != want { - t.Fatalf("json.Marshal = %s, want %s", got, want) - } - }) - } -} - -// In v1, JSON serialization silently ignored invalid UTF-8 by -// replacing such bytes with the Unicode replacement character. -// In v2, JSON serialization reports an error if invalid UTF-8 is encountered. -// -// Users of v2 can opt into the v1 behavior by setting [AllowInvalidUTF8]. -// -// Silently allowing invalid UTF-8 causes data corruption that can be difficult -// to detect until it is too late. Once it has been discovered, strict UTF-8 -// behavior sometimes cannot be enabled since other logic may be depending -// on the current behavior due to Hyrum's Law. -// -// Tim Bray, the author of RFC 8259 recommends that implementations should -// go beyond RFC 8259 and instead target compliance with RFC 7493, -// which makes strict decisions about behavior left undefined in RFC 8259. -// In particular, RFC 7493 rejects the presence of invalid UTF-8. -// See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90 -func TestInvalidUTF8(t *testing.T) { - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - got, err := json.Marshal("\xff") - switch { - case json.Version == "v1" && err != nil: - t.Fatalf("json.Marshal error: %v", err) - case json.Version == "v1" && string(got) != "\"\ufffd\"": - t.Fatalf(`json.Marshal = %s, want %q`, got, "\ufffd") - case json.Version == "v2" && err == nil: - t.Fatal("json.Marshal error is nil, want non-nil") - } - }) - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - const in = "\"\xff\"" - var got string - err := json.Unmarshal([]byte(in), &got) - switch { - case json.Version == "v1" && err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case json.Version == "v1" && got != "\ufffd": - t.Fatalf(`json.Unmarshal = %q, want "\ufffd"`, got) - case json.Version == "v2" && err == nil: - t.Fatal("json.Unmarshal error is nil, want non-nil") - } - }) - } -} - -// In v1, duplicate JSON object names are permitted by default where -// they follow the inconsistent and difficult-to-explain merge semantics of v1. -// In v2, duplicate JSON object names are rejected by default where -// they follow the merge semantics of v2 based on RFC 7396. -// -// Users of v2 can opt into the v1 behavior by setting [AllowDuplicateNames]. -// -// Per RFC 8259, the handling of duplicate names is left as undefined behavior. -// Rejecting such inputs is within the realm of valid behavior. -// Tim Bray, the author of RFC 8259 recommends that implementations should -// go beyond RFC 8259 and instead target compliance with RFC 7493, -// which makes strict decisions about behavior left undefined in RFC 8259. -// In particular, RFC 7493 rejects the presence of duplicate object names. -// See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90 -// -// The lack of duplicate name rejection has correctness implications where -// roundtrip unmarshal/marshal do not result in semantically equivalent JSON. -// This is surprising behavior for users when they accidentally -// send JSON objects with duplicate names. -// -// The lack of duplicate name rejection may have security implications since it -// becomes difficult for a security tool to validate the semantic meaning of a -// JSON object since meaning is undefined in the presence of duplicate names. -// See https://labs.bishopfox.com/tech-blog/an-exploration-of-json-interoperability-vulnerabilities -// -// Related issue: -// -// https://go.dev/issue/48298 -func TestDuplicateNames(t *testing.T) { - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - const in = `{"Name":1,"Name":2}` - var got struct{ Name int } - err := json.Unmarshal([]byte(in), &got) - switch { - case json.Version == "v1" && err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case json.Version == "v1" && got != struct{ Name int }{2}: - t.Fatalf(`json.Unmarshal = %v, want {2}`, got) - case json.Version == "v2" && err == nil: - t.Fatal("json.Unmarshal error is nil, want non-nil") - } - }) - } -} - -// In v1, unmarshaling a JSON null into a non-empty value was inconsistent -// in that sometimes it would be ignored and other times clear the value. -// In v2, unmarshaling a JSON null into a non-empty value would consistently -// always clear the value regardless of the value's type. -// -// The purpose of this change is to have consistent behavior with how JSON nulls -// are handled during Unmarshal. This semantic detail has no effect -// when Unmarshaling into a empty value. -// -// Related issues: -// -// https://go.dev/issue/22177 -// https://go.dev/issue/33835 -func TestMergeNull(t *testing.T) { - type Types struct { - Bool bool - String string - Bytes []byte - Int int - Map map[string]string - Struct struct{ Field string } - Slice []string - Array [1]string - Pointer *string - Interface any - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - // Start with a non-empty value where all fields are populated. - in := Types{ - Bool: true, - String: "old", - Bytes: []byte("old"), - Int: 1234, - Map: map[string]string{"old": "old"}, - Struct: struct{ Field string }{"old"}, - Slice: []string{"old"}, - Array: [1]string{"old"}, - Pointer: new(string), - Interface: "old", - } - - // Unmarshal a JSON null into every field. - if err := json.Unmarshal([]byte(`{ - "Bool": null, - "String": null, - "Bytes": null, - "Int": null, - "Map": null, - "Struct": null, - "Slice": null, - "Array": null, - "Pointer": null, - "Interface": null - }`), &in); err != nil { - t.Fatalf("json.Unmarshal error: %v", err) - } - - want := map[string]Types{ - "v1": { - Bool: true, - String: "old", - Int: 1234, - Struct: struct{ Field string }{"old"}, - Array: [1]string{"old"}, - }, - "v2": {}, // all fields are zeroed - }[json.Version] - if !reflect.DeepEqual(in, want) { - t.Fatalf("json.Unmarshal = %+v, want %+v", in, want) - } - }) - } -} - -// In v1, merge semantics are inconsistent and difficult to explain. -// In v2, merge semantics replaces the destination value for anything -// other than a JSON object, and recursively merges JSON objects. -// -// Merge semantics in v1 are inconsistent and difficult to explain -// largely because the behavior came about organically, rather than -// having a principled approach to how the semantics should operate. -// In v2, merging follows behavior based on RFC 7396. -// -// Related issues: -// -// https://go.dev/issue/21092 -// https://go.dev/issue/26946 -// https://go.dev/issue/27172 -// https://go.dev/issue/30701 -// https://go.dev/issue/31924 -// https://go.dev/issue/43664 -func TestMergeComposite(t *testing.T) { - type Tuple struct{ Old, New bool } - type Composites struct { - Slice []Tuple - Array [1]Tuple - Map map[string]Tuple - MapPointer map[string]*Tuple - Struct struct{ Tuple Tuple } - StructPointer *struct{ Tuple Tuple } - Interface any - InterfacePointer any - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - // Start with a non-empty value where all fields are populated. - in := Composites{ - Slice: []Tuple{{Old: true}, {Old: true}}[:1], - Array: [1]Tuple{{Old: true}}, - Map: map[string]Tuple{"Tuple": {Old: true}}, - MapPointer: map[string]*Tuple{"Tuple": {Old: true}}, - Struct: struct{ Tuple Tuple }{Tuple{Old: true}}, - StructPointer: &struct{ Tuple Tuple }{Tuple{Old: true}}, - Interface: Tuple{Old: true}, - InterfacePointer: &Tuple{Old: true}, - } - - // Unmarshal into every pre-populated field. - if err := json.Unmarshal([]byte(`{ - "Slice": [{"New":true}, {"New":true}], - "Array": [{"New":true}], - "Map": {"Tuple": {"New":true}}, - "MapPointer": {"Tuple": {"New":true}}, - "Struct": {"Tuple": {"New":true}}, - "StructPointer": {"Tuple": {"New":true}}, - "Interface": {"New":true}, - "InterfacePointer": {"New":true} - }`), &in); err != nil { - t.Fatalf("json.Unmarshal error: %v", err) - } - - merged := Tuple{Old: true, New: true} - replaced := Tuple{Old: false, New: true} - want := map[string]Composites{ - "v1": { - Slice: []Tuple{merged, merged}, // merged - Array: [1]Tuple{merged}, // merged - Map: map[string]Tuple{"Tuple": replaced}, // replaced - MapPointer: map[string]*Tuple{"Tuple": &replaced}, // replaced - Struct: struct{ Tuple Tuple }{merged}, // merged (same as v2) - StructPointer: &struct{ Tuple Tuple }{merged}, // merged (same as v2) - Interface: map[string]any{"New": true}, // replaced - InterfacePointer: &merged, // merged (same as v2) - }, - "v2": { - Slice: []Tuple{replaced, replaced}, // replaced - Array: [1]Tuple{replaced}, // replaced - Map: map[string]Tuple{"Tuple": merged}, // merged - MapPointer: map[string]*Tuple{"Tuple": &merged}, // merged - Struct: struct{ Tuple Tuple }{merged}, // merged (same as v1) - StructPointer: &struct{ Tuple Tuple }{merged}, // merged (same as v1) - Interface: merged, // merged - InterfacePointer: &merged, // merged (same as v1) - }, - }[json.Version] - if !reflect.DeepEqual(in, want) { - t.Fatalf("json.Unmarshal = %+v, want %+v", in, want) - } - }) - } -} - -// In v1, there was no special support for time.Duration, -// which resulted in that type simply being treated as a signed integer. -// In v2, there is now first-class support for time.Duration, where the type is -// formatted and parsed using time.Duration.String and time.ParseDuration. -// -// Users of v2 can opt into the v1 behavior by setting -// the "format:nano" option in the `json` struct field tag: -// -// struct { -// Duration time.Duration `json:",format:nano"` -// } -// -// Related issue: -// -// https://go.dev/issue/10275 -func TestTimeDurations(t *testing.T) { - t.SkipNow() // TODO(https://go.dev/issue/71631): The default representation of time.Duration is still undecided. - for _, json := range jsonPackages { - t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - got, err := json.Marshal(time.Minute) - switch { - case err != nil: - t.Fatalf("json.Marshal error: %v", err) - case json.Version == "v1" && string(got) != "60000000000": - t.Fatalf("json.Marshal = %s, want 60000000000", got) - case json.Version == "v2" && string(got) != `"1m0s"`: - t.Fatalf(`json.Marshal = %s, want "1m0s"`, got) - } - }) - } - - for _, json := range jsonPackages { - t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - in := map[string]string{ - "v1": "60000000000", - "v2": `"1m0s"`, - }[json.Version] - var got time.Duration - err := json.Unmarshal([]byte(in), &got) - switch { - case err != nil: - t.Fatalf("json.Unmarshal error: %v", err) - case got != time.Minute: - t.Fatalf("json.Unmarshal = %v, want 1m0s", got) - } - }) - } -} - -// In v1, non-empty structs without any JSON serializable fields are permitted. -// In v2, non-empty structs without any JSON serializable fields are rejected. -// -// The purpose of this change is to avoid a common pitfall for new users -// where they expect JSON serialization to handle unexported fields. -// However, this does not work since Go reflection does not -// provide the package the ability to mutate such fields. -// Rejecting unserializable structs in v2 is intended to be a clear signal -// that the type is not supposed to be serialized. -func TestEmptyStructs(t *testing.T) { - never := func(string) bool { return false } - onlyV2 := func(v string) bool { return v == "v2" } - values := []struct { - in any - wantError func(string) bool - }{ - // It is okay to marshal a truly empty struct in v1 and v2. - {in: addr(struct{}{}), wantError: never}, - // In v1, a non-empty struct without exported fields - // is equivalent to an empty struct, but is rejected in v2. - // Note that errors.errorString type has only unexported fields. - {in: errors.New("error"), wantError: onlyV2}, - // A mix of exported and unexported fields is permitted. - {in: addr(struct{ Exported, unexported int }{}), wantError: never}, - } - - for _, json := range jsonPackages { - t.Run("Marshal", func(t *testing.T) { - for _, value := range values { - wantError := value.wantError(json.Version) - _, err := json.Marshal(value.in) - switch { - case (err == nil) && wantError: - t.Fatalf("json.Marshal error is nil, want non-nil") - case (err != nil) && !wantError: - t.Fatalf("json.Marshal error: %v", err) - } - } - }) - } - - for _, json := range jsonPackages { - t.Run("Unmarshal", func(t *testing.T) { - for _, value := range values { - wantError := value.wantError(json.Version) - out := reflect.New(reflect.TypeOf(value.in).Elem()).Interface() - err := json.Unmarshal([]byte("{}"), out) - switch { - case (err == nil) && wantError: - t.Fatalf("json.Unmarshal error is nil, want non-nil") - case (err != nil) && !wantError: - t.Fatalf("json.Unmarshal error: %v", err) - } - } - }) - } -} diff --git a/pkg/encoders/json/v2_encode_test.go b/pkg/encoders/json/v2_encode_test.go deleted file mode 100644 index 11c5218..0000000 --- a/pkg/encoders/json/v2_encode_test.go +++ /dev/null @@ -1,1430 +0,0 @@ -// Copyright 2011 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. - -//go:build goexperiment.jsonv2 - -package json - -import ( - "bytes" - "encoding" - "fmt" - "log" - "math" - "reflect" - "regexp" - "runtime/debug" - "strconv" - "sync" - "testing" - "testing/synctest" - "time" -) - -type OptionalsEmpty struct { - 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"` - - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitempty"` - - Mr map[string]any `json:"mr"` - Mo map[string]any `json:",omitempty"` - - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitempty"` - - Br bool `json:"br"` - Bo bool `json:"bo,omitempty"` - - Ur uint `json:"ur"` - Uo uint `json:"uo,omitempty"` - - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty"` -} - -func TestOmitEmpty(t *testing.T) { - const want = `{ - "sr": "", - "omitempty": 0, - "slr": null, - "mr": {}, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "sto": {} -}` - var o OptionalsEmpty - o.Sw = "something" - o.Mr = map[string]any{} - o.Mo = map[string]any{} - - got, err := MarshalIndent(&o, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -type NonZeroStruct struct{} - -func (nzs NonZeroStruct) IsZero() bool { - return false -} - -type NoPanicStruct struct { - Int int `json:"int,omitzero"` -} - -func (nps *NoPanicStruct) IsZero() bool { - return nps.Int != 0 -} - -type isZeroer interface { - IsZero() bool -} - -type OptionalsZero struct { - Sr string `json:"sr"` - So string `json:"so,omitzero"` - Sw string `json:"-"` - - Ir int `json:"omitzero"` // actually named omitzero, not an option - Io int `json:"io,omitzero"` - - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitzero"` - SloNonNil []string `json:"slononnil,omitzero"` - - Mr map[string]any `json:"mr"` - Mo map[string]any `json:",omitzero"` - Moo map[string]any `json:"moo,omitzero"` - - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitzero"` - Foo float64 `json:"foo,omitzero"` - Foo2 [2]float64 `json:"foo2,omitzero"` - - Br bool `json:"br"` - Bo bool `json:"bo,omitzero"` - - Ur uint `json:"ur"` - Uo uint `json:"uo,omitzero"` - - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitzero"` - - Time time.Time `json:"time,omitzero"` - TimeLocal time.Time `json:"timelocal,omitzero"` - Nzs NonZeroStruct `json:"nzs,omitzero"` - - NilIsZeroer isZeroer `json:"niliszeroer,omitzero"` // nil interface - NonNilIsZeroer isZeroer `json:"nonniliszeroer,omitzero"` // non-nil interface - NoPanicStruct0 isZeroer `json:"nps0,omitzero"` // non-nil interface with nil pointer - NoPanicStruct1 isZeroer `json:"nps1,omitzero"` // non-nil interface with non-nil pointer - NoPanicStruct2 *NoPanicStruct `json:"nps2,omitzero"` // nil pointer - NoPanicStruct3 *NoPanicStruct `json:"nps3,omitzero"` // non-nil pointer - NoPanicStruct4 NoPanicStruct `json:"nps4,omitzero"` // concrete type -} - -func TestOmitZero(t *testing.T) { - const want = `{ - "sr": "", - "omitzero": 0, - "slr": null, - "slononnil": [], - "mr": {}, - "Mo": {}, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "nzs": {}, - "nps1": {}, - "nps3": {}, - "nps4": {} -}` - var o OptionalsZero - o.Sw = "something" - o.SloNonNil = make([]string, 0) - o.Mr = map[string]any{} - o.Mo = map[string]any{} - - o.Foo = -0 - o.Foo2 = [2]float64{+0, -0} - - o.TimeLocal = time.Time{}.Local() - - o.NonNilIsZeroer = time.Time{} - o.NoPanicStruct0 = (*NoPanicStruct)(nil) - o.NoPanicStruct1 = &NoPanicStruct{} - o.NoPanicStruct3 = &NoPanicStruct{} - - got, err := MarshalIndent(&o, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -func TestOmitZeroMap(t *testing.T) { - const want = `{ - "foo": { - "sr": "", - "omitzero": 0, - "slr": null, - "mr": null, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "nzs": {}, - "nps4": {} - } -}` - m := map[string]OptionalsZero{"foo": {}} - got, err := MarshalIndent(m, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - fmt.Println(got) - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -type OptionalsEmptyZero struct { - Sr string `json:"sr"` - So string `json:"so,omitempty,omitzero"` - Sw string `json:"-"` - - Io int `json:"io,omitempty,omitzero"` - - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitempty,omitzero"` - SloNonNil []string `json:"slononnil,omitempty,omitzero"` - - Mr map[string]any `json:"mr"` - Mo map[string]any `json:",omitempty,omitzero"` - - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitempty,omitzero"` - - Br bool `json:"br"` - Bo bool `json:"bo,omitempty,omitzero"` - - Ur uint `json:"ur"` - Uo uint `json:"uo,omitempty,omitzero"` - - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty,omitzero"` - - Time time.Time `json:"time,omitempty,omitzero"` - Nzs NonZeroStruct `json:"nzs,omitempty,omitzero"` -} - -func TestOmitEmptyZero(t *testing.T) { - const want = `{ - "sr": "", - "slr": null, - "mr": {}, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "nzs": {} -}` - var o OptionalsEmptyZero - o.Sw = "something" - o.SloNonNil = make([]string, 0) - o.Mr = map[string]any{} - o.Mo = map[string]any{} - - got, err := MarshalIndent(&o, "", " ") - if err != nil { - t.Fatalf("MarshalIndent error: %v", err) - } - if got := string(got); got != want { - t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want)) - } -} - -type StringTag struct { - BoolStr bool `json:",string"` - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - StrStr string `json:",string"` - NumberStr Number `json:",string"` -} - -func TestRoundtripStringTag(t *testing.T) { - tests := []struct { - CaseName - in StringTag - want string // empty to just test that we roundtrip - }{{ - CaseName: 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. - CaseName: 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": "\"\\b\\f\\n\\r\\t\\\"\\\\\"", - "NumberStr": "0" -}`, - }} - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - got, err := MarshalIndent(&tt.in, "", "\t") - if err != nil { - t.Fatalf("%s: MarshalIndent error: %v", tt.Where, err) - } - if got := string(got); got != tt.want { - t.Fatalf("%s: MarshalIndent:\n\tgot: %s\n\twant: %s", tt.Where, stripWhitespace(got), stripWhitespace(tt.want)) - } - - // Verify that it round-trips. - var s2 StringTag - if err := Unmarshal(got, &s2); err != nil { - t.Fatalf("%s: Decode error: %v", tt.Where, err) - } - if !reflect.DeepEqual(s2, tt.in) { - t.Fatalf("%s: Decode:\n\tinput: %s\n\tgot: %#v\n\twant: %#v", tt.Where, indentNewlines(string(got)), s2, tt.in) - } - }) - } -} - -// byte slices are special even if they're renamed types. -type renamedByte byte -type renamedByteSlice []byte -type renamedRenamedByteSlice []renamedByte - -func TestEncodeRenamedByteSlice(t *testing.T) { - s := renamedByteSlice("abc") - got, err := Marshal(s) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - want := `"YWJj"` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } - r := renamedRenamedByteSlice("abc") - got, err = Marshal(r) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -type SamePointerNoCycle struct { - Ptr1, Ptr2 *SamePointerNoCycle -} - -var samePointerNoCycle = &SamePointerNoCycle{} - -type PointerCycle struct { - Ptr *PointerCycle -} - -var pointerCycle = &PointerCycle{} - -type PointerCycleIndirect struct { - Ptrs []any -} - -type RecursiveSlice []RecursiveSlice - -var ( - pointerCycleIndirect = &PointerCycleIndirect{} - mapCycle = make(map[string]any) - sliceCycle = []any{nil} - sliceNoCycle = []any{nil, nil} - recursiveSliceCycle = []RecursiveSlice{nil} -) - -func init() { - ptr := &SamePointerNoCycle{} - samePointerNoCycle.Ptr1 = ptr - samePointerNoCycle.Ptr2 = ptr - - pointerCycle.Ptr = pointerCycle - pointerCycleIndirect.Ptrs = []any{pointerCycleIndirect} - - mapCycle["x"] = mapCycle - sliceCycle[0] = sliceCycle - sliceNoCycle[1] = sliceNoCycle[:1] - const startDetectingCyclesAfter = 1e3 - for i := startDetectingCyclesAfter; i > 0; i-- { - sliceNoCycle = []any{sliceNoCycle} - } - recursiveSliceCycle[0] = recursiveSliceCycle -} - -func TestSamePointerNoCycle(t *testing.T) { - if _, err := Marshal(samePointerNoCycle); err != nil { - t.Fatalf("Marshal error: %v", err) - } -} - -func TestSliceNoCycle(t *testing.T) { - if _, err := Marshal(sliceNoCycle); err != nil { - t.Fatalf("Marshal error: %v", err) - } -} - -func TestUnsupportedValues(t *testing.T) { - tests := []struct { - CaseName - in any - }{ - {Name(""), math.NaN()}, - {Name(""), math.Inf(-1)}, - {Name(""), math.Inf(1)}, - {Name(""), pointerCycle}, - {Name(""), pointerCycleIndirect}, - {Name(""), mapCycle}, - {Name(""), sliceCycle}, - {Name(""), recursiveSliceCycle}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - if _, err := Marshal(tt.in); err != nil { - if _, ok := err.(*UnsupportedValueError); !ok { - t.Errorf("%s: Marshal error:\n\tgot: %T\n\twant: %T", tt.Where, err, new(UnsupportedValueError)) - } - } else { - t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where) - } - }) - } -} - -// Issue 43207 -func TestMarshalTextFloatMap(t *testing.T) { - m := map[textfloat]string{ - textfloat(math.NaN()): "1", - textfloat(math.NaN()): "1", - } - got, err := Marshal(m) - if err != nil { - t.Errorf("Marshal error: %v", err) - } - want := `{"TF:NaN":"1","TF:NaN":"1"}` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -// Ref has Marshaler and Unmarshaler methods with pointer receiver. -type Ref int - -func (*Ref) MarshalJSON() ([]byte, error) { - return []byte(`"ref"`), nil -} - -func (r *Ref) UnmarshalJSON([]byte) error { - *r = 12 - return nil -} - -// Val has Marshaler methods with value receiver. -type Val int - -func (Val) MarshalJSON() ([]byte, error) { - 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 -} - -func (r *RefText) UnmarshalText([]byte) error { - *r = 13 - return nil -} - -// ValText has Marshaler methods with value receiver. -type ValText int - -func (ValText) MarshalText() ([]byte, error) { - 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 error: %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -// C implements Marshaler and returns unescaped JSON. -type C int - -func (C) MarshalJSON() ([]byte, error) { - return []byte(`"<&>"`), nil -} - -// CText implements Marshaler and returns unescaped text. -type CText int - -func (CText) MarshalText() ([]byte, error) { - return []byte(`"<&>"`), nil -} - -func TestMarshalerEscaping(t *testing.T) { - var c C - want := `"\u003c\u0026\u003e"` - b, err := Marshal(c) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } - - var ct CText - want = `"\"\u003c\u0026\u003e\""` - b, err = Marshal(ct) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestAnonymousFields(t *testing.T) { - tests := []struct { - CaseName - 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. - CaseName: Name("AmbiguousField"), - makeInput: func() any { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - } - ) - return S{S1{1, 2}, S2{3, 4}} - }, - want: `{}`, - }, { - CaseName: Name("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() any { - 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. - CaseName: Name("UnexportedEmbeddedInt"), - makeInput: func() any { - type ( - myInt int - S struct{ myInt } - ) - return S{5} - }, - want: `{}`, - }, { - // Exported embedded field of non-struct type should be serialized. - CaseName: Name("ExportedEmbeddedInt"), - makeInput: func() any { - 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. - CaseName: Name("UnexportedEmbeddedIntPointer"), - makeInput: func() any { - 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. - CaseName: Name("ExportedEmbeddedIntPointer"), - makeInput: func() any { - 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. - CaseName: Name("EmbeddedStruct"), - makeInput: func() any { - 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. - CaseName: Name("EmbeddedStructPointer"), - makeInput: func() any { - 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. - CaseName: Name("NestedStructAndInts"), - makeInput: func() any { - 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. - CaseName: Name("EmbeddedFieldBehindNilPointer"), - makeInput: func() any { - type ( - S2 struct{ Field string } - S struct{ *S2 } - ) - return S{} - }, - want: `{}`, - }} - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.makeInput()) - if err != nil { - t.Fatalf("%s: Marshal error: %v", tt.Where, err) - } - if string(b) != tt.want { - t.Fatalf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, b, tt.want) - } - }) - } -} - -type BugA struct { - S string -} - -type BugB struct { - BugA - S string -} - -type BugC struct { - S string -} - -// Legal Go: We never use the repeated embedded field (S). -type BugX struct { - A int - BugA - BugB -} - -// golang.org/issue/16042. -// Even if a nil interface value is passed in, as long as -// it implements Marshaler, it should be marshaled. -type nilJSONMarshaler string - -func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { - if nm == nil { - return Marshal("0zenil0") - } - return Marshal("zenil:" + string(*nm)) -} - -// golang.org/issue/34235. -// Even if a nil interface value is passed in, as long as -// it implements encoding.TextMarshaler, it should be marshaled. -type nilTextMarshaler string - -func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { - 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) { - tests := []struct { - CaseName - in any - want string - }{ - {Name(""), nil, `null`}, - {Name(""), new(float64), `0`}, - {Name(""), []any(nil), `null`}, - {Name(""), []string(nil), `null`}, - {Name(""), map[string]string(nil), `null`}, - {Name(""), []byte(nil), `null`}, - {Name(""), struct{ M string }{"gopher"}, `{"M":"gopher"}`}, - {Name(""), struct{ M Marshaler }{}, `{"M":null}`}, - {Name(""), struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, `{"M":"0zenil0"}`}, - {Name(""), struct{ M any }{(*nilJSONMarshaler)(nil)}, `{"M":null}`}, - {Name(""), struct{ M encoding.TextMarshaler }{}, `{"M":null}`}, - {Name(""), struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, `{"M":"0zenil0"}`}, - {Name(""), struct{ M any }{(*nilTextMarshaler)(nil)}, `{"M":null}`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - switch got, err := Marshal(tt.in); { - case err != nil: - t.Fatalf("%s: Marshal error: %v", tt.Where, err) - case string(got) != tt.want: - t.Fatalf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) - } - }) - } -} - -// Issue 5245. -func TestEmbeddedBug(t *testing.T) { - v := BugB{ - BugA{"A"}, - "B", - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal error:", err) - } - want := `{"S":"B"}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %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 error:", err) - } - want = `{"A":23}` - got = string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -type BugD struct { // Same as BugA after tagging. - XXX string `json:"S"` -} - -// BugD's tagged S field should dominate BugA's. -type BugY struct { - 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 error:", err) - } - want := `{"S":"BugD"}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %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. -} - -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 error:", err) - } - want := `{}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestIssue10281(t *testing.T) { - type Foo struct { - N Number - } - x := Foo{Number(`invalid`)} - - if _, err := Marshal(&x); err == nil { - t.Fatalf("Marshal error: got nil, want non-nil") - } -} - -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 _, err := Marshal(dummy); err == nil { - t.Errorf("Marshal error: got nil, want non-nil") - } - - type Data struct { - A string - I int - } - want := Data{A: "a", I: 1} - b, err := Marshal(want) - if err != nil { - t.Errorf("Marshal error: %v", err) - } - - var got Data - if err := Unmarshal(b, &got); err != nil { - t.Errorf("Unmarshal error: %v", err) - } - if got != want { - t.Errorf("Unmarshal:\n\tgot: %v\n\twant: %v", got, want) - } -} - -func TestHTMLEscape(t *testing.T) { - var b, want bytes.Buffer - m := `{"M":"foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `"}` - want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) - HTMLEscape(&b, []byte(m)) - if !bytes.Equal(b.Bytes(), want.Bytes()) { - t.Errorf("HTMLEscape:\n\tgot: %s\n\twant: %s", b.Bytes(), want.Bytes()) - } -} - -// 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 error: %v", err) - } - if got, want := string(b), `{"n":"42"}`; got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } - var back stringPointer - switch err = Unmarshal(b, &back); { - case err != nil: - t.Fatalf("Unmarshal error: %v", err) - case back.N == nil: - t.Fatalf("Unmarshal: back.N = nil, want non-nil") - case *back.N != 42: - t.Fatalf("Unmarshal: *back.N = %d, want 42", *back.N) - } -} - -var encodeStringTests = []struct { - in string - out string -}{ - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\b"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\f"`}, - {"\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) error: %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 - -func (b jsonbyte) MarshalJSON() ([]byte, error) { return tenc(`{"JB":%d}`, b) } - -type textbyte byte - -func (b textbyte) MarshalText() ([]byte, error) { return tenc(`TB:%d`, b) } - -type jsonint int - -func (i jsonint) MarshalJSON() ([]byte, error) { return tenc(`{"JI":%d}`, i) } - -type textint int - -func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } - -func tenc(format string, a ...any) ([]byte, error) { - var buf bytes.Buffer - fmt.Fprintf(&buf, format, a...) - return buf.Bytes(), nil -} - -type textfloat float64 - -func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) } - -// Issue 13783 -func TestEncodeBytekind(t *testing.T) { - tests := []struct { - CaseName - in any - want string - }{ - {Name(""), byte(7), "7"}, - {Name(""), jsonbyte(7), `{"JB":7}`}, - {Name(""), textbyte(4), `"TB:4"`}, - {Name(""), jsonint(5), `{"JI":5}`}, - {Name(""), textint(1), `"TI:1"`}, - {Name(""), []byte{0, 1}, `"AAE="`}, - {Name(""), []jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, - {Name(""), [][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, - {Name(""), []textbyte{2, 3}, `["TB:2","TB:3"]`}, - {Name(""), []jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, - {Name(""), []textint{9, 3}, `["TI:9","TI:3"]`}, - {Name(""), []int{9, 3}, `[9,3]`}, - {Name(""), []textfloat{12, 3}, `["TF:12.00","TF:3.00"]`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.in) - if err != nil { - t.Errorf("%s: Marshal error: %v", tt.Where, err) - } - got, want := string(b), tt.want - if got != want { - t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, got, want) - } - }) - } -} - -func TestTextMarshalerMapKeysAreSorted(t *testing.T) { - got, err := Marshal(map[unmarshalerText]int{ - {"x", "y"}: 1, - {"y", "x"}: 2, - {"a", "z"}: 3, - {"z", "a"}: 4, - }) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -// https://golang.org/issue/33675 -func TestNilMarshalerTextMapKey(t *testing.T) { - got, err := Marshal(map[*unmarshalerText]int{ - (*unmarshalerText)(nil): 1, - {"A", "B"}: 2, - }) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - const want = `{"":1,"A:B":2}` - if string(got) != want { - t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, 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-9]`), // positive exponent must be signed - 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 - // below here for float32 only - re(`[1-9][0-9]{8}[1-9]`), // too many significant digits in integer - re(`[1-9][0-9.]{9}[1-9]`), // too many significant digits in decimal -} - -func TestMarshalFloat(t *testing.T) { - t.Parallel() - nfail := 0 - test := func(f float64, bits int) { - vf := any(f) - if bits == 32 { - f = float64(float32(f)) // round - vf = float32(f) - } - bout, err := Marshal(vf) - if err != nil { - t.Errorf("Marshal(%T(%g)) error: %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("ParseFloat(%q) error: %v", out, err) - nfail++ - return - } - if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0 - t.Errorf("ParseFloat(%q):\n\tgot: %g\n\twant: %g", out, float32(g), vf) - nfail++ - return - } - - bad := badFloatREs - if bits == 64 { - bad = bad[:len(bad)-2] - } - for _, re := range bad { - 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 RawMessage `json:",omitempty"` - } - T2 struct { - M *RawMessage `json:",omitempty"` - } - ) - - var ( - rawNil = RawMessage(nil) - rawEmpty = RawMessage([]byte{}) - rawText = RawMessage([]byte(`"foo"`)) - ) - - tests := []struct { - CaseName - in any - want string - ok bool - }{ - // Test with nil RawMessage. - {Name(""), rawNil, "null", true}, - {Name(""), &rawNil, "null", true}, - {Name(""), []any{rawNil}, "[null]", true}, - {Name(""), &[]any{rawNil}, "[null]", true}, - {Name(""), []any{&rawNil}, "[null]", true}, - {Name(""), &[]any{&rawNil}, "[null]", true}, - {Name(""), struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {Name(""), &struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {Name(""), struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {Name(""), &struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {Name(""), map[string]any{"M": rawNil}, `{"M":null}`, true}, - {Name(""), &map[string]any{"M": rawNil}, `{"M":null}`, true}, - {Name(""), map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {Name(""), &map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {Name(""), T1{rawNil}, "{}", true}, - {Name(""), T2{&rawNil}, `{"M":null}`, true}, - {Name(""), &T1{rawNil}, "{}", true}, - {Name(""), &T2{&rawNil}, `{"M":null}`, true}, - - // Test with empty, but non-nil, RawMessage. - {Name(""), rawEmpty, "", false}, - {Name(""), &rawEmpty, "", false}, - {Name(""), []any{rawEmpty}, "", false}, - {Name(""), &[]any{rawEmpty}, "", false}, - {Name(""), []any{&rawEmpty}, "", false}, - {Name(""), &[]any{&rawEmpty}, "", false}, - {Name(""), struct{ X RawMessage }{rawEmpty}, "", false}, - {Name(""), &struct{ X RawMessage }{rawEmpty}, "", false}, - {Name(""), struct{ X *RawMessage }{&rawEmpty}, "", false}, - {Name(""), &struct{ X *RawMessage }{&rawEmpty}, "", false}, - {Name(""), map[string]any{"nil": rawEmpty}, "", false}, - {Name(""), &map[string]any{"nil": rawEmpty}, "", false}, - {Name(""), map[string]any{"nil": &rawEmpty}, "", false}, - {Name(""), &map[string]any{"nil": &rawEmpty}, "", false}, - {Name(""), T1{rawEmpty}, "{}", true}, - {Name(""), T2{&rawEmpty}, "", false}, - {Name(""), &T1{rawEmpty}, "{}", true}, - {Name(""), &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 - {Name(""), rawText, `"foo"`, true}, // Issue6458 - {Name(""), &rawText, `"foo"`, true}, - {Name(""), []any{rawText}, `["foo"]`, true}, // Issue6458 - {Name(""), &[]any{rawText}, `["foo"]`, true}, // Issue6458 - {Name(""), []any{&rawText}, `["foo"]`, true}, - {Name(""), &[]any{&rawText}, `["foo"]`, true}, - {Name(""), struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), &struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, - {Name(""), struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {Name(""), &struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {Name(""), map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), &map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {Name(""), &map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {Name(""), T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {Name(""), T2{&rawText}, `{"M":"foo"}`, true}, - {Name(""), &T1{rawText}, `{"M":"foo"}`, true}, - {Name(""), &T2{&rawText}, `{"M":"foo"}`, true}, - } - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.in) - if ok := (err == nil); ok != tt.ok { - if err != nil { - t.Errorf("%s: Marshal error: %v", tt.Where, err) - } else { - t.Errorf("%s: Marshal error: got nil, want non-nil", tt.Where) - } - } - if got := string(b); got != tt.want { - t.Errorf("%s: Marshal:\n\tinput: %#v\n\tgot: %s\n\twant: %s", tt.Where, tt.in, got, tt.want) - } - }) - } -} - -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") -} - -func TestMarshalUncommonFieldNames(t *testing.T) { - v := struct { - A0, À, Aβ int - }{} - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal error:", err) - } - want := `{"A0":0,"À":0,"Aβ":0}` - got := string(b) - if got != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestMarshalerError(t *testing.T) { - s := "test variable" - st := reflect.TypeOf(s) - const errText = "json: test error" - - tests := []struct { - CaseName - err *MarshalerError - want string - }{{ - Name(""), - &MarshalerError{st, fmt.Errorf(errText), ""}, - "json: error calling MarshalJSON for type " + st.String() + ": " + errText, - }, { - Name(""), - &MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}, - "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, - }} - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - got := tt.err.Error() - if got != tt.want { - t.Errorf("%s: Error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) - } - }) - } -} - -type marshaledValue string - -func (v marshaledValue) MarshalJSON() ([]byte, error) { - return []byte(v), nil -} - -func TestIssue63379(t *testing.T) { - for _, v := range []string{ - "[]<", - "[]>", - "[]&", - "[]\u2028", - "[]\u2029", - "{}<", - "{}>", - "{}&", - "{}\u2028", - "{}\u2029", - } { - _, err := Marshal(marshaledValue(v)) - if err == nil { - t.Errorf("expected error for %q", v) - } - } -} - -// Issue #73733: encoding/json used a WaitGroup to coordinate access to cache entries. -// Since WaitGroup.Wait is durably blocking, this caused apparent deadlocks when -// multiple bubbles called json.Marshal at the same time. -func TestSynctestMarshal(t *testing.T) { - var wg sync.WaitGroup - for range 5 { - wg.Go(func() { - synctest.Test(t, func(t *testing.T) { - _, err := Marshal([]string{}) - if err != nil { - t.Errorf("Marshal: %v", err) - } - }) - }) - } - wg.Wait() -} diff --git a/pkg/encoders/json/v2_example_marshaling_test.go b/pkg/encoders/json/v2_example_marshaling_test.go deleted file mode 100644 index 21de7f1..0000000 --- a/pkg/encoders/json/v2_example_marshaling_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// 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. - -//go:build goexperiment.jsonv2 - -package json_test - -import ( - "fmt" - "log" - "strings" - - "encoding/json" -) - -type Animal int - -const ( - Unknown Animal = iota - Gopher - Zebra -) - -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: - *a = Unknown - case "gopher": - *a = Gopher - case "zebra": - *a = Zebra - } - - return nil -} - -func (a Animal) MarshalJSON() ([]byte, error) { - var s string - switch a { - default: - s = "unknown" - case Gopher: - s = "gopher" - case Zebra: - s = "zebra" - } - - return json.Marshal(s) -} - -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) - } - - census := make(map[Animal]int) - for _, animal := range zoo { - census[animal] += 1 - } - - fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n", - census[Gopher], census[Zebra], census[Unknown]) - - // Output: - // Zoo Census: - // * Gophers: 3 - // * Zebras: 2 - // * Unknown: 3 -} diff --git a/pkg/encoders/json/v2_example_test.go b/pkg/encoders/json/v2_example_test.go deleted file mode 100644 index 52f89e2..0000000 --- a/pkg/encoders/json/v2_example_test.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright 2011 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. - -//go:build goexperiment.jsonv2 - -package json_test - -import ( - "bytes" - "fmt" - "io" - "log" - "os" - "strings" - - "encoding/json" -) - -func ExampleMarshal() { - type ColorGroup struct { - ID int - Name string - Colors []string - } - group := ColorGroup{ - ID: 1, - Name: "Reds", - Colors: []string{"Crimson", "Red", "Ruby", "Maroon"}, - } - b, err := json.Marshal(group) - if err != nil { - fmt.Println("error:", err) - } - os.Stdout.Write(b) - // Output: - // {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]} -} - -func ExampleUnmarshal() { - var jsonBlob = []byte(`[ - {"Name": "Platypus", "Order": "Monotremata"}, - {"Name": "Quoll", "Order": "Dasyuromorphia"} -]`) - type Animal struct { - Name string - Order string - } - var animals []Animal - err := json.Unmarshal(jsonBlob, &animals) - if err != nil { - fmt.Println("error:", err) - } - fmt.Printf("%+v", animals) - // Output: - // [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}] -} - -// This example uses a Decoder to decode a stream of distinct JSON values. -func ExampleDecoder() { - const jsonStream = ` - {"Name": "Ed", "Text": "Knock knock."} - {"Name": "Sam", "Text": "Who's there?"} - {"Name": "Ed", "Text": "Go fmt."} - {"Name": "Sam", "Text": "Go fmt who?"} - {"Name": "Ed", "Text": "Go fmt yourself!"} -` - type Message struct { - Name, Text string - } - dec := json.NewDecoder(strings.NewReader(jsonStream)) - for { - var m Message - if err := dec.Decode(&m); err == io.EOF { - break - } else if err != nil { - log.Fatal(err) - } - fmt.Printf("%s: %s\n", m.Name, m.Text) - } - // Output: - // Ed: Knock knock. - // Sam: Who's there? - // Ed: Go fmt. - // Sam: Go fmt who? - // Ed: Go fmt yourself! -} - -// This example uses a Decoder to decode a stream of distinct JSON values. -func ExampleDecoder_Token() { - const jsonStream = ` - {"Message": "Hello", "Array": [1, 2, 3], "Null": null, "Number": 1.234} -` - dec := json.NewDecoder(strings.NewReader(jsonStream)) - for { - t, err := dec.Token() - if err == io.EOF { - break - } - if err != nil { - log.Fatal(err) - } - fmt.Printf("%T: %v", t, t) - if dec.More() { - fmt.Printf(" (more)") - } - fmt.Printf("\n") - } - // Output: - // json.Delim: { (more) - // string: Message (more) - // string: Hello (more) - // string: Array (more) - // json.Delim: [ (more) - // float64: 1 (more) - // float64: 2 (more) - // float64: 3 - // json.Delim: ] (more) - // string: Null (more) - // : (more) - // string: Number (more) - // float64: 1.234 - // json.Delim: } -} - -// This example uses a Decoder to decode a streaming array of JSON objects. -func ExampleDecoder_Decode_stream() { - const jsonStream = ` - [ - {"Name": "Ed", "Text": "Knock knock."}, - {"Name": "Sam", "Text": "Who's there?"}, - {"Name": "Ed", "Text": "Go fmt."}, - {"Name": "Sam", "Text": "Go fmt who?"}, - {"Name": "Ed", "Text": "Go fmt yourself!"} - ] -` - type Message struct { - Name, Text string - } - dec := json.NewDecoder(strings.NewReader(jsonStream)) - - // read open bracket - t, err := dec.Token() - if err != nil { - log.Fatal(err) - } - fmt.Printf("%T: %v\n", t, t) - - // while the array contains values - for dec.More() { - var m Message - // decode an array value (Message) - err := dec.Decode(&m) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%v: %v\n", m.Name, m.Text) - } - - // read closing bracket - t, err = dec.Token() - if err != nil { - log.Fatal(err) - } - fmt.Printf("%T: %v\n", t, t) - - // Output: - // json.Delim: [ - // Ed: Knock knock. - // Sam: Who's there? - // Ed: Go fmt. - // Sam: Go fmt who? - // Ed: Go fmt yourself! - // json.Delim: ] -} - -// This example uses RawMessage to delay parsing part of a JSON message. -func ExampleRawMessage_unmarshal() { - type Color struct { - Space string - Point json.RawMessage // delay parsing until we know the color space - } - type RGB struct { - R uint8 - G uint8 - B uint8 - } - type YCbCr struct { - Y uint8 - Cb int8 - Cr int8 - } - - var j = []byte(`[ - {"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}}, - {"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}} -]`) - var colors []Color - err := json.Unmarshal(j, &colors) - if err != nil { - log.Fatalln("error:", err) - } - - for _, c := range colors { - var dst any - switch c.Space { - case "RGB": - dst = new(RGB) - case "YCbCr": - dst = new(YCbCr) - } - err := json.Unmarshal(c.Point, dst) - if err != nil { - log.Fatalln("error:", err) - } - fmt.Println(c.Space, dst) - } - // Output: - // YCbCr &{255 0 -10} - // RGB &{98 218 255} -} - -// This example uses RawMessage to use a precomputed JSON during marshal. -func ExampleRawMessage_marshal() { - h := json.RawMessage(`{"precomputed": true}`) - - c := struct { - Header *json.RawMessage `json:"header"` - Body string `json:"body"` - }{Header: &h, Body: "Hello Gophers!"} - - b, err := json.MarshalIndent(&c, "", "\t") - if err != nil { - fmt.Println("error:", err) - } - os.Stdout.Write(b) - - // Output: - // { - // "header": { - // "precomputed": true - // }, - // "body": "Hello Gophers!" - // } -} - -func ExampleIndent() { - type Road struct { - Name string - Number int - } - roads := []Road{ - {"Diamond Fork", 29}, - {"Sheep Creek", 51}, - } - - b, err := json.Marshal(roads) - if err != nil { - log.Fatal(err) - } - - var out bytes.Buffer - json.Indent(&out, b, "=", "\t") - out.WriteTo(os.Stdout) - // Output: - // [ - // = { - // = "Name": "Diamond Fork", - // = "Number": 29 - // = }, - // = { - // = "Name": "Sheep Creek", - // = "Number": 51 - // = } - // =] -} - -func ExampleMarshalIndent() { - data := map[string]int{ - "a": 1, - "b": 2, - } - - b, err := json.MarshalIndent(data, "", "") - if err != nil { - log.Fatal(err) - } - - fmt.Println(string(b)) - // Output: - // { - // "a": 1, - // "b": 2 - // } -} - -func ExampleValid() { - goodJSON := `{"example": 1}` - badJSON := `{"example":2:]}}` - - fmt.Println(json.Valid([]byte(goodJSON)), json.Valid([]byte(badJSON))) - // Output: - // true false -} - -func ExampleHTMLEscape() { - var out bytes.Buffer - json.HTMLEscape(&out, []byte(`{"Name":"HTML content"}`)) - out.WriteTo(os.Stdout) - // Output: - //{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"} -} diff --git a/pkg/encoders/json/v2_example_text_marshaling_test.go b/pkg/encoders/json/v2_example_text_marshaling_test.go deleted file mode 100644 index dbac57d..0000000 --- a/pkg/encoders/json/v2_example_text_marshaling_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// 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. - -//go:build goexperiment.jsonv2 - -package json_test - -import ( - "fmt" - "log" - "strings" - - "encoding/json" -) - -type Size int - -const ( - Unrecognized Size = iota - Small - Large -) - -func (s *Size) UnmarshalText(text []byte) error { - switch strings.ToLower(string(text)) { - default: - *s = Unrecognized - case "small": - *s = Small - case "large": - *s = Large - } - return nil -} - -func (s Size) MarshalText() ([]byte, error) { - var name string - switch s { - default: - name = "unrecognized" - case Small: - name = "small" - case Large: - name = "large" - } - return []byte(name), 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) - } - - counts := make(map[Size]int) - for _, size := range inventory { - counts[size] += 1 - } - - fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n", - counts[Small], counts[Large], counts[Unrecognized]) - - // Output: - // Inventory Counts: - // * Small: 3 - // * Large: 2 - // * Unrecognized: 3 -} diff --git a/pkg/encoders/json/v2_fuzz_test.go b/pkg/encoders/json/v2_fuzz_test.go deleted file mode 100644 index 0a151b8..0000000 --- a/pkg/encoders/json/v2_fuzz_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// 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. - -//go:build goexperiment.jsonv2 - -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() any{ - func() any { return new(any) }, - func() any { return new(map[string]any) }, - func() any { return new([]any) }, - } { - 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 - } - } - }) -} diff --git a/pkg/encoders/json/v2_scanner_test.go b/pkg/encoders/json/v2_scanner_test.go deleted file mode 100644 index bec5521..0000000 --- a/pkg/encoders/json/v2_scanner_test.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2010 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. - -//go:build goexperiment.jsonv2 - -package json - -import ( - "bytes" - "math" - "math/rand" - "reflect" - "strings" - "testing" -) - -func indentNewlines(s string) string { - return strings.Join(strings.Split(s, "\n"), "\n\t") -} - -func stripWhitespace(s string) string { - return strings.Map(func(r rune) rune { - if r == ' ' || r == '\n' || r == '\r' || r == '\t' { - return -1 - } - return r - }, s) -} - -func TestValid(t *testing.T) { - tests := []struct { - CaseName - data string - ok bool - }{ - {Name(""), `foo`, false}, - {Name(""), `}{`, false}, - {Name(""), `{]`, false}, - {Name(""), `{}`, true}, - {Name(""), `{"foo":"bar"}`, true}, - {Name(""), `{"foo":"bar","bar":{"baz":["qux"]}}`, true}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - if ok := Valid([]byte(tt.data)); ok != tt.ok { - t.Errorf("%s: Valid(`%s`) = %v, want %v", tt.Where, tt.data, ok, tt.ok) - } - }) - } -} - -func TestCompactAndIndent(t *testing.T) { - tests := []struct { - CaseName - compact string - indent string - }{ - {Name(""), `1`, `1`}, - {Name(""), `{}`, `{}`}, - {Name(""), `[]`, `[]`}, - {Name(""), `{"":2}`, "{\n\t\"\": 2\n}"}, - {Name(""), `[3]`, "[\n\t3\n]"}, - {Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, - {Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"}, - {Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[ - true, - false, - null, - "x", - 1, - 1.5, - 0, - -5e+2 -]`}, - {Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 - } - var buf bytes.Buffer - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - buf.Reset() - if err := Compact(&buf, []byte(tt.compact)); err != nil { - t.Errorf("%s: Compact error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.compact { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) - } - - buf.Reset() - if err := Compact(&buf, []byte(tt.indent)); err != nil { - t.Errorf("%s: Compact error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.compact { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) - } - - buf.Reset() - if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { - t.Errorf("%s: Indent error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.indent { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) - } - - buf.Reset() - if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { - t.Errorf("%s: Indent error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.indent { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent)) - } - }) - } -} - -func TestCompactSeparators(t *testing.T) { - // U+2028 and U+2029 should be escaped inside strings. - // They should not appear outside strings. - tests := []struct { - CaseName - in, compact string - }{ - {Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"}, - {Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - var buf bytes.Buffer - if err := Compact(&buf, []byte(tt.in)); err != nil { - t.Errorf("%s: Compact error: %v", tt.Where, err) - } else if got := buf.String(); got != tt.compact { - t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact)) - } - }) - } -} - -// Tests of a large random structure. - -func TestCompactBig(t *testing.T) { - initBig() - var buf bytes.Buffer - if err := Compact(&buf, jsonBig); err != nil { - t.Fatalf("Compact error: %v", err) - } - b := buf.Bytes() - if !bytes.Equal(b, jsonBig) { - t.Error("Compact:") - diff(t, b, jsonBig) - return - } -} - -func TestIndentBig(t *testing.T) { - t.Parallel() - initBig() - var buf bytes.Buffer - if err := Indent(&buf, jsonBig, "", "\t"); err != nil { - t.Fatalf("Indent error: %v", err) - } - b := buf.Bytes() - if len(b) == len(jsonBig) { - // jsonBig is compact (no unnecessary spaces); - // indenting should make it bigger - t.Fatalf("Indent did not expand the input") - } - - // should be idempotent - var buf1 bytes.Buffer - if err := Indent(&buf1, b, "", "\t"); err != nil { - t.Fatalf("Indent error: %v", err) - } - b1 := buf1.Bytes() - if !bytes.Equal(b1, b) { - t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):") - diff(t, b1, b) - return - } - - // should get back to original - buf1.Reset() - if err := Compact(&buf1, b); err != nil { - t.Fatalf("Compact error: %v", err) - } - b1 = buf1.Bytes() - if !bytes.Equal(b1, jsonBig) { - t.Error("Compact(Indent(jsonBig)) != jsonBig:") - diff(t, b1, jsonBig) - return - } -} - -func TestIndentErrors(t *testing.T) { - tests := []struct { - CaseName - in string - err error - }{ - {Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", len64(`{"X": "foo", "Y"`)}}, - {Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", len64(`{"X": "foo" `)}}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - slice := make([]uint8, 0) - buf := bytes.NewBuffer(slice) - if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { - if !reflect.DeepEqual(err, tt.err) { - t.Fatalf("%s: Indent error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) - } - } - }) - } -} - -func diff(t *testing.T, a, b []byte) { - t.Helper() - for i := 0; ; i++ { - if i >= len(a) || i >= len(b) || a[i] != b[i] { - j := i - 10 - if j < 0 { - j = 0 - } - t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) - return - } - } -} - -func trim(b []byte) []byte { - return b[:min(len(b), 20)] -} - -// Generate a random JSON object. - -var jsonBig []byte - -func initBig() { - n := 10000 - if testing.Short() { - n = 100 - } - b, err := Marshal(genValue(n)) - if err != nil { - panic(err) - } - jsonBig = b -} - -func genValue(n int) any { - if n > 1 { - switch rand.Intn(2) { - case 0: - return genArray(n) - case 1: - return genMap(n) - } - } - switch rand.Intn(3) { - case 0: - return rand.Intn(2) == 0 - case 1: - return rand.NormFloat64() - case 2: - return genString(30) - } - panic("unreachable") -} - -func genString(stddev float64) string { - n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) - c := make([]rune, n) - for i := range c { - f := math.Abs(rand.NormFloat64()*64 + 32) - if f > 0x10ffff { - f = 0x10ffff - } - c[i] = rune(f) - } - return string(c) -} - -func genArray(n int) []any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if f < 1 { - f = 1 - } - 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]any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if n > 0 && f == 0 { - f = 1 - } - x := make(map[string]any) - for i := 0; i < f; i++ { - x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) - } - return x -} diff --git a/pkg/encoders/json/v2_stream_test.go b/pkg/encoders/json/v2_stream_test.go deleted file mode 100644 index 38eb666..0000000 --- a/pkg/encoders/json/v2_stream_test.go +++ /dev/null @@ -1,504 +0,0 @@ -// Copyright 2010 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. - -//go:build goexperiment.jsonv2 - -package json - -import ( - "bytes" - "io" - "log" - "net" - "net/http" - "net/http/httptest" - "reflect" - "runtime/debug" - "strings" - "testing" - - "encoding/json/internal/jsontest" -) - -type CaseName = jsontest.CaseName -type CasePos = jsontest.CasePos - -var Name = jsontest.Name - -// Test values for the stream test. -// One of each JSON kind. -var streamTest = []any{ - 0.1, - "hello", - nil, - true, - false, - []any{"a", "b", "c"}, - map[string]any{"K": "Kelvin", "ß": "long s"}, - 3.14, // another value to make sure something can follow map -} - -var streamEncoded = `0.1 -"hello" -null -true -false -["a","b","c"] -{"ß":"long s","K":"Kelvin"} -3.14 -` - -func TestEncoder(t *testing.T) { - for i := 0; i <= len(streamTest); i++ { - var buf strings.Builder - enc := NewEncoder(&buf) - // Check that enc.SetIndent("", "") turns off indentation. - enc.SetIndent(">", ".") - enc.SetIndent("", "") - for j, v := range streamTest[0:i] { - if err := enc.Encode(v); err != nil { - t.Fatalf("#%d.%d Encode error: %v", i, j, err) - } - } - if got, want := buf.String(), nlines(streamEncoded, i); got != want { - t.Errorf("encoding %d items: mismatch:", i) - diff(t, []byte(got), []byte(want)) - break - } - } -} - -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) error: got nil, want non-nil") - } - - type Data struct { - A string - I int - } - want := Data{A: "a", I: 1} - if err := enc.Encode(want); err != nil { - t.Errorf("Marshal error: %v", err) - } - - var got Data - if err := Unmarshal(buf.Bytes(), &got); err != nil { - t.Errorf("Unmarshal error: %v", err) - } - if got != want { - t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %v\n\twant: %v", got, want) - } -} - -var streamEncodedIndent = `0.1 -"hello" -null -true -false -[ ->."a", ->."b", ->."c" ->] -{ ->."ß": "long s", ->."K": "Kelvin" ->} -3.14 -` - -func TestEncoderIndent(t *testing.T) { - var buf strings.Builder - enc := NewEncoder(&buf) - enc.SetIndent(">", ".") - for _, v := range streamTest { - enc.Encode(v) - } - if got, want := buf.String(), streamEncodedIndent; got != want { - t.Errorf("Encode mismatch:\ngot:\n%s\n\nwant:\n%s", got, want) - diff(t, []byte(got), []byte(want)) - } -} - -type strMarshaler string - -func (s strMarshaler) MarshalJSON() ([]byte, error) { - return []byte(s), nil -} - -type strPtrMarshaler string - -func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) { - return []byte(*s), nil -} - -func TestEncoderSetEscapeHTML(t *testing.T) { - var c C - var ct CText - var tagStruct struct { - Valid int `json:"<>&#! "` - Invalid int `json:"\\"` - } - - // This case is particularly interesting, as we force the encoder to - // take the address of the Ptr field to use its MarshalJSON method. This - // is why the '&' is important. - marshalerStruct := &struct { - NonPtr strMarshaler - Ptr strPtrMarshaler - }{`""`, `""`} - - // https://golang.org/issue/34154 - stringOption := struct { - Bar string `json:"bar,string"` - }{`foobar`} - - tests := []struct { - CaseName - v any - wantEscape string - want string - }{ - {Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`}, - {Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, - {Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, - { - Name("tagStruct"), tagStruct, - `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`, - `{"<>&#! ":0,"Invalid":0}`, - }, - { - Name(`""`), marshalerStruct, - `{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`, - `{"NonPtr":"","Ptr":""}`, - }, - { - Name("stringOption"), stringOption, - `{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`, - `{"bar":"\"foobar\""}`, - }, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - var buf strings.Builder - enc := NewEncoder(&buf) - if err := enc.Encode(tt.v); err != nil { - t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err) - } - if got := strings.TrimSpace(buf.String()); got != tt.wantEscape { - t.Errorf("%s: Encode(%s):\n\tgot: %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape) - } - buf.Reset() - enc.SetEscapeHTML(false) - if err := enc.Encode(tt.v); err != nil { - t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err) - } - if got := strings.TrimSpace(buf.String()); got != tt.want { - t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot: %s\n\twant: %s", - tt.Where, tt.Name, got, tt.want) - } - }) - } -} - -func TestDecoder(t *testing.T) { - for i := 0; i <= len(streamTest); i++ { - // Use stream without newlines as input, - // just to stress the decoder even more. - // Our test input does not include back-to-back numbers. - // Otherwise stripping the newlines would - // merge two adjacent JSON values. - var buf bytes.Buffer - for _, c := range nlines(streamEncoded, i) { - if c != '\n' { - buf.WriteRune(c) - } - } - out := make([]any, i) - dec := NewDecoder(&buf) - for j := range out { - if err := dec.Decode(&out[j]); err != nil { - t.Fatalf("decode #%d/%d error: %v", j, i, err) - } - } - if !reflect.DeepEqual(out, streamTest[0:i]) { - t.Errorf("decoding %d items: mismatch:", i) - for j := range out { - if !reflect.DeepEqual(out[j], streamTest[j]) { - t.Errorf("#%d:\n\tgot: %v\n\twant: %v", j, out[j], streamTest[j]) - } - } - break - } - } -} - -func TestDecoderBuffered(t *testing.T) { - r := strings.NewReader(`{"Name": "Gopher"} extra `) - var m struct { - Name string - } - d := NewDecoder(r) - err := d.Decode(&m) - if err != nil { - t.Fatal(err) - } - if m.Name != "Gopher" { - t.Errorf("Name = %s, want Gopher", m.Name) - } - rest, err := io.ReadAll(d.Buffered()) - if err != nil { - t.Fatal(err) - } - if got, want := string(rest), " extra "; got != want { - t.Errorf("Remaining = %s, want %s", got, want) - } -} - -func nlines(s string, n int) string { - if n <= 0 { - return "" - } - for i, c := range s { - if c == '\n' { - if n--; n == 0 { - return s[0 : i+1] - } - } - } - return s -} - -func TestRawMessage(t *testing.T) { - var data struct { - X float64 - Id RawMessage - Y float32 - } - const raw = `["\u0056",null]` - const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}` - err := Unmarshal([]byte(want), &data) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if string([]byte(data.Id)) != raw { - t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", []byte(data.Id), raw) - } - got, err := Marshal(&data) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestNullRawMessage(t *testing.T) { - var data struct { - X float64 - Id RawMessage - IdPtr *RawMessage - Y float32 - } - const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}` - err := Unmarshal([]byte(want), &data) - if err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if want, got := "null", string(data.Id); want != got { - t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", got, want) - } - if data.IdPtr != nil { - t.Fatalf("pointer mismatch: got non-nil, want nil") - } - got, err := Marshal(&data) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if string(got) != want { - t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want) - } -} - -func TestBlocking(t *testing.T) { - tests := []struct { - CaseName - in string - }{ - {Name(""), `{"x": 1}`}, - {Name(""), `[1, 2, 3]`}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - r, w := net.Pipe() - go w.Write([]byte(tt.in)) - var val any - - // If Decode reads beyond what w.Write writes above, - // it will block, and the test will deadlock. - if err := NewDecoder(r).Decode(&val); err != nil { - t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err) - } - r.Close() - w.Close() - }) - } -} - -type decodeThis struct { - v any -} - -func TestDecodeInStream(t *testing.T) { - tests := []struct { - CaseName - json string - expTokens []any - }{ - // streaming token cases - {CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}}, - {CaseName: Name(""), json: ` [10] `, expTokens: []any{ - Delim('['), float64(10), Delim(']')}}, - {CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{ - Delim('['), false, float64(10), "b", Delim(']')}}, - {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{ - Delim('{'), "a", float64(1), Delim('}')}}, - {CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{ - Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, - {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ - Delim('['), - Delim('{'), "a", float64(1), Delim('}'), - Delim('{'), "a", float64(2), Delim('}'), - Delim(']')}}, - {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{ - Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), - Delim('}')}}, - {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{ - Delim('{'), "obj", Delim('['), - Delim('{'), "a", float64(1), Delim('}'), - Delim(']'), Delim('}')}}, - - // streaming tokens with intermittent Decode() - {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{ - Delim('{'), "a", - decodeThis{float64(1)}, - Delim('}')}}, - {CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - Delim(']')}}, - {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - decodeThis{map[string]any{"a": float64(2)}}, - Delim(']')}}, - {CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ - Delim('{'), "obj", Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - Delim(']'), Delim('}')}}, - - {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{ - Delim('{'), "obj", - decodeThis{map[string]any{"a": float64(1)}}, - Delim('}')}}, - {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{ - Delim('{'), "obj", - decodeThis{[]any{ - map[string]any{"a": float64(1)}, - }}, - Delim('}')}}, - {CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ - Delim('['), - decodeThis{map[string]any{"a": float64(1)}}, - decodeThis{&SyntaxError{"invalid character '{' after array element", len64(` [{"a": 1} `)}}, - }}, - {CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{ - Delim('{'), strings.Repeat("a", 513), - decodeThis{&SyntaxError{"invalid character '1' after object key", len64(`{ "` + strings.Repeat("a", 513) + `" `)}}, - }}, - {CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{ - Delim('{'), - &SyntaxError{"invalid escape sequence `\\a` in string", len64(`{ "`)}, - }}, - {CaseName: Name(""), json: ` \a`, expTokens: []any{ - &SyntaxError{"invalid character '\\\\' looking for beginning of value", len64(` `)}, - }}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - dec := NewDecoder(strings.NewReader(tt.json)) - for i, want := range tt.expTokens { - var got any - var err error - - if dt, ok := want.(decodeThis); ok { - want = dt.v - err = dec.Decode(&got) - } else { - got, err = dec.Token() - } - if errWant, ok := want.(error); ok { - if err == nil || !reflect.DeepEqual(err, errWant) { - t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant) - } - break - } else if err != nil { - t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err) - } - if !reflect.DeepEqual(got, want) { - t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot: %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want) - } - } - }) - } -} - -// Test from golang.org/issue/11893 -func TestHTTPDecoding(t *testing.T) { - const raw = `{ "foo": "bar" }` - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(raw)) - })) - defer ts.Close() - res, err := http.Get(ts.URL) - if err != nil { - log.Fatalf("http.Get error: %v", err) - } - defer res.Body.Close() - - foo := struct { - Foo string - }{} - - d := NewDecoder(res.Body) - err = d.Decode(&foo) - if err != nil { - t.Fatalf("Decode error: %v", err) - } - if foo.Foo != "bar" { - t.Errorf(`Decode: got %q, want "bar"`, foo.Foo) - } - - // make sure we get the EOF the second time - err = d.Decode(&foo) - if err != io.EOF { - t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err) - } -} diff --git a/pkg/encoders/json/v2_tagkey_test.go b/pkg/encoders/json/v2_tagkey_test.go deleted file mode 100644 index 3963a5e..0000000 --- a/pkg/encoders/json/v2_tagkey_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2011 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. - -//go:build goexperiment.jsonv2 - -package json - -import "testing" - -type basicLatin2xTag struct { - V string `json:"$%-/"` -} - -type basicLatin3xTag struct { - V string `json:"0123456789"` -} - -type basicLatin4xTag struct { - V string `json:"ABCDEFGHIJKLMO"` -} - -type basicLatin5xTag struct { - V string `json:"PQRSTUVWXYZ_"` -} - -type basicLatin6xTag struct { - V string `json:"abcdefghijklmno"` -} - -type basicLatin7xTag struct { - V string `json:"pqrstuvwxyz"` -} - -type miscPlaneTag struct { - V string `json:"色は匂へど"` -} - -type percentSlashTag struct { - V string `json:"text/html%"` // https://golang.org/issue/2718 -} - -type punctuationTag struct { - V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` // https://golang.org/issue/3546 -} - -type dashTag struct { - V string `json:"-,"` -} - -type emptyTag struct { - W string -} - -type misnamedTag struct { - X string `jsom:"Misnamed"` -} - -type badFormatTag struct { - Y string `:"BadFormat"` -} - -type badCodeTag struct { - Z string `json:" !\"#&'()*+,."` -} - -type spaceTag struct { - Q string `json:"With space"` -} - -type unicodeTag struct { - W string `json:"Ελλάδα"` -} - -func TestStructTagObjectKey(t *testing.T) { - tests := []struct { - CaseName - raw any - value string - key string - }{ - {Name(""), basicLatin2xTag{"2x"}, "2x", "$%-/"}, - {Name(""), basicLatin3xTag{"3x"}, "3x", "0123456789"}, - {Name(""), basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, - {Name(""), basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, - {Name(""), basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, - {Name(""), basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, - {Name(""), miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, - {Name(""), dashTag{"foo"}, "foo", "-"}, - {Name(""), emptyTag{"Pour Moi"}, "Pour Moi", "W"}, - {Name(""), misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, - {Name(""), badFormatTag{"Orfevre"}, "Orfevre", "Y"}, - {Name(""), badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, - {Name(""), percentSlashTag{"brut"}, "brut", "text/html%"}, - {Name(""), punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, - {Name(""), spaceTag{"Perreddu"}, "Perreddu", "With space"}, - {Name(""), unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, - } - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - b, err := Marshal(tt.raw) - if err != nil { - t.Fatalf("%s: Marshal error: %v", tt.Where, err) - } - var f any - err = Unmarshal(b, &f) - if err != nil { - t.Fatalf("%s: Unmarshal error: %v", tt.Where, err) - } - for k, v := range f.(map[string]any) { - if k == tt.key { - if s, ok := v.(string); !ok || s != tt.value { - t.Fatalf("%s: Unmarshal(%#q) value:\n\tgot: %q\n\twant: %q", tt.Where, b, s, tt.value) - } - } else { - t.Fatalf("%s: Unmarshal(%#q): unexpected key: %q", tt.Where, b, k) - } - } - }) - } -} diff --git a/pkg/encoders/tag/tag.go b/pkg/encoders/tag/tag.go index 064392d..e9c33ae 100644 --- a/pkg/encoders/tag/tag.go +++ b/pkg/encoders/tag/tag.go @@ -4,6 +4,8 @@ package tag import ( + "bytes" + "lol.mleku.dev/errorf" "next.orly.dev/pkg/encoders/text" "next.orly.dev/pkg/utils/bufpool" @@ -25,11 +27,23 @@ func New(t ...[]byte) *T { return &T{T: t, b: bufpool.Get()} } +func NewWithCap(c int) *T { + return &T{T: make([][]byte, 0, c), b: bufpool.Get()} +} + func (t *T) Free() { bufpool.Put(t.b) t.T = nil } +func (t *T) Len() int { return len(t.T) } + +func (t *T) Less(i, j int) bool { + return bytes.Compare(t.T[i], t.T[j]) < 0 +} + +func (t *T) Swap(i, j int) { t.T[i], t.T[j] = t.T[j], t.T[i] } + // Marshal encodes a tag.T as standard minified JSON array of strings. // // Call bufpool.PutBytes(b) to return the buffer to the bufpool after use. diff --git a/pkg/encoders/tag/tag_test.go b/pkg/encoders/tag/tag_test.go index b4e6802..b5c9cc9 100644 --- a/pkg/encoders/tag/tag_test.go +++ b/pkg/encoders/tag/tag_test.go @@ -17,14 +17,14 @@ func TestMarshalUnmarshal(t *testing.T) { _, _ = frand.Read(b1) tg.T = append(tg.T, b1) } - tb := tg.Marshal() + tb := tg.Marshal(nil) var tbc []byte tbc = append(tbc, tb...) tg2 := New() if _, err := tg2.Unmarshal(tb); chk.E(err) { t.Fatal(err) } - tb2 := tg2.Marshal() + tb2 := tg2.Marshal(nil) if !utils.FastEqual(tbc, tb2) { t.Fatalf("failed to re-marshal back original") } diff --git a/pkg/encoders/tag/tags.go b/pkg/encoders/tag/tags.go index 425007c..8c871c8 100644 --- a/pkg/encoders/tag/tags.go +++ b/pkg/encoders/tag/tags.go @@ -1,6 +1,8 @@ package tag import ( + "bytes" + "lol.mleku.dev/chk" "next.orly.dev/pkg/utils/bufpool" ) @@ -9,6 +11,26 @@ import ( // no uniqueness constraint (not a set). type S []*T +func NewSWithCap(c int) (s *S) { + ss := make([]*T, 0, c) + return (*S)(&ss) +} + +func (s *S) Len() int { + return len(*s) +} + +func (s *S) Less(i, j int) bool { + // only the first element is compared, this is only used for normalizing + // filters and the individual tags must be separately sorted. + return bytes.Compare((*s)[i].T[0], (*s)[j].T[0]) < 0 +} + +func (s *S) Swap(i, j int) { + // TODO implement me + panic("implement me") +} + // MarshalJSON encodes a tags.T appended to a provided byte slice in JSON form. // // Call bufpool.PutBytes(b) to return the buffer to the bufpool after use. diff --git a/pkg/utils/bufpool/bufpool_test.go b/pkg/utils/bufpool/bufpool_test.go deleted file mode 100644 index 0bfec55..0000000 --- a/pkg/utils/bufpool/bufpool_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package bufpool - -import ( - "testing" -) - -func TestBufferPoolGetPut(t *testing.T) { - // Get a buffer from the pool - buf1 := Get() - - // Verify the buffer is the correct size - if len(*buf1) != BufferSize { - t.Errorf("Expected buffer size of %d, got %d", BufferSize, len(*buf1)) - } - - // Write some data to the buffer - (*buf1)[0] = 42 - - // Return the buffer to the pool - Put(buf1) - - // Get another buffer, which should be the same one we just returned - buf2 := Get() - - // Buffer may or may not be cleared, but we should be able to use it - // Let's check if we have the expected buffer size - if len(*buf2) != BufferSize { - t.Errorf("Expected buffer size of %d, got %d", BufferSize, len(*buf2)) - } -} - -func TestMultipleBuffers(t *testing.T) { - // Get multiple buffers at once to ensure the pool can handle it - const numBuffers = 10 - buffers := make([]B, numBuffers) - - // Get buffers from the pool - for i := 0; i < numBuffers; i++ { - buffers[i] = Get() - // Verify each buffer is the correct size - if len(*buffers[i]) != BufferSize { - t.Errorf( - "Buffer %d: Expected size of %d, got %d", i, BufferSize, - len(*buffers[i]), - ) - } - } - - // Return all buffers to the pool - for i := 0; i < numBuffers; i++ { - Put(buffers[i]) - } -} - -func BenchmarkGetPut(b *testing.B) { - for i := 0; i < b.N; i++ { - buf := Get() - Put(buf) - } -} - -func BenchmarkGetPutParallel(b *testing.B) { - b.RunParallel( - func(pb *testing.PB) { - for pb.Next() { - buf := Get() - Put(buf) - } - }, - ) -} diff --git a/pkg/utils/pointers/pointers.go b/pkg/utils/pointers/pointers.go new file mode 100644 index 0000000..6e4c089 --- /dev/null +++ b/pkg/utils/pointers/pointers.go @@ -0,0 +1,23 @@ +package pointers + +import ( + "time" + + "next.orly.dev/pkg/encoders/timestamp" +) + +// PointerToValue is a generic interface (type constraint) to refer to any +// pointer to almost any kind of common type of value. +// +// see the utils/values package for a set of methods to accept these values and +// return the correct type pointer to them. +type PointerToValue interface { + ~*uint | ~*int | ~*uint8 | ~*uint16 | ~*uint32 | ~*uint64 | ~*int8 | ~*int16 | ~*int32 | + ~*int64 | ~*float32 | ~*float64 | ~*string | ~*[]string | ~*time.Time | ~*time.Duration | + ~*[]byte | ~*[][]byte | ~*timestamp.T +} + +// Present determines whether there is a value for a PointerToValue type. +func Present[V PointerToValue](i V) bool { + return i != nil +} diff --git a/pkg/utils/values/values.go b/pkg/utils/values/values.go new file mode 100644 index 0000000..b479d91 --- /dev/null +++ b/pkg/utils/values/values.go @@ -0,0 +1,95 @@ +package values + +import ( + "time" +) + +// ToUintPointer returns a pointer to the uint value passed in. +func ToUintPointer(v uint) *uint { + return &v +} + +// ToIntPointer returns a pointer to the int value passed in. +func ToIntPointer(v int) *int { + return &v +} + +// ToUint8Pointer returns a pointer to the uint8 value passed in. +func ToUint8Pointer(v uint8) *uint8 { + return &v +} + +// ToUint16Pointer returns a pointer to the uint16 value passed in. +func ToUint16Pointer(v uint16) *uint16 { + return &v +} + +// ToUint32Pointer returns a pointer to the uint32 value passed in. +func ToUint32Pointer(v uint32) *uint32 { + return &v +} + +// ToUint64Pointer returns a pointer to the uint64 value passed in. +func ToUint64Pointer(v uint64) *uint64 { + return &v +} + +// ToInt8Pointer returns a pointer to the int8 value passed in. +func ToInt8Pointer(v int8) *int8 { + return &v +} + +// ToInt16Pointer returns a pointer to the int16 value passed in. +func ToInt16Pointer(v int16) *int16 { + return &v +} + +// ToInt32Pointer returns a pointer to the int32 value passed in. +func ToInt32Pointer(v int32) *int32 { + return &v +} + +// ToInt64Pointer returns a pointer to the int64 value passed in. +func ToInt64Pointer(v int64) *int64 { + return &v +} + +// ToFloat32Pointer returns a pointer to the float32 value passed in. +func ToFloat32Pointer(v float32) *float32 { + return &v +} + +// ToFloat64Pointer returns a pointer to the float64 value passed in. +func ToFloat64Pointer(v float64) *float64 { + return &v +} + +// ToStringPointer returns a pointer to the string value passed in. +func ToStringPointer(v string) *string { + return &v +} + +// ToStringSlicePointer returns a pointer to the []string value passed in. +func ToStringSlicePointer(v []string) *[]string { + return &v +} + +// ToTimePointer returns a pointer to the time.Time value passed in. +func ToTimePointer(v time.Time) *time.Time { + return &v +} + +// ToDurationPointer returns a pointer to the time.Duration value passed in. +func ToDurationPointer(v time.Duration) *time.Duration { + return &v +} + +// ToBytesPointer returns a pointer to the []byte value passed in. +func ToBytesPointer(v []byte) *[]byte { + return &v +} + +// ToByteSlicesPointer returns a pointer to the [][]byte value passed in. +func ToByteSlicesPointer(v [][]byte) *[][]byte { + return &v +}