47 changed files with 687 additions and 19133 deletions
@ -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","<pubkey1>","<pubkey3"],["#t","hashtag","stuff"]]
|
||||||
|
//
|
||||||
|
for _, tg := range *f.Tags { |
||||||
|
if tg == nil { |
||||||
|
// nothing here
|
||||||
|
continue |
||||||
|
} |
||||||
|
if tg.Len() < 2 { |
||||||
|
// must have at least key and one value
|
||||||
|
continue |
||||||
|
} |
||||||
|
tKey := tg.T[0] |
||||||
|
if len(tKey) != 1 || |
||||||
|
((tKey[0] < 'a' || tKey[0] > '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 |
||||||
|
} |
||||||
@ -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 |
||||||
|
} |
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
|
||||||
} |
|
||||||
@ -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)
|
|
||||||
// <nil>: <nil> (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, "<prefix>", "<indent>") |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
fmt.Println(string(b)) |
|
||||||
// Output:
|
|
||||||
// {
|
|
||||||
// <prefix><indent>"a": 1,
|
|
||||||
// <prefix><indent>"b": 2
|
|
||||||
// <prefix>}
|
|
||||||
} |
|
||||||
|
|
||||||
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":"<b>HTML content</b>"}`)) |
|
||||||
out.WriteTo(os.Stdout) |
|
||||||
// Output:
|
|
||||||
//{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"}
|
|
||||||
} |
|
||||||
@ -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
|
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
@ -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 |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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 <script> injection.
|
|
||||||
//
|
|
||||||
// This example shows how to obtain equivalent behavior provided by the
|
|
||||||
// v1 [encoding/json] package that is no longer directly supported by this package.
|
|
||||||
// Newly written code that intermix JSON and HTML should instead be using the
|
|
||||||
// [github.com/google/safehtml] module for safety purposes.
|
|
||||||
func ExampleEscapeForHTML() { |
|
||||||
page := struct { |
|
||||||
Title string |
|
||||||
Body string |
|
||||||
}{ |
|
||||||
Title: "Example Embedded Javascript", |
|
||||||
Body: `<script> console.log("Hello, world!"); </script>`, |
|
||||||
} |
|
||||||
|
|
||||||
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"
|
|
||||||
// }
|
|
||||||
} |
|
||||||
@ -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
|
|
||||||
}) |
|
||||||
} |
|
||||||
@ -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") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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: "<invalid jsontext.Token>"}}, |
|
||||||
{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) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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 |
|
||||||
} |
|
||||||
@ -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 |
|
||||||
}{`"<str>"`, `"<str>"`} |
|
||||||
|
|
||||||
// https://golang.org/issue/34154
|
|
||||||
stringOption := struct { |
|
||||||
Bar string `json:"bar,string"` |
|
||||||
}{`<html>foobar</html>`} |
|
||||||
|
|
||||||
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(`"<str>"`), marshalerStruct, |
|
||||||
`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`, |
|
||||||
`{"NonPtr":"<str>","Ptr":"<str>"}`, |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name("stringOption"), stringOption, |
|
||||||
`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`, |
|
||||||
`{"bar":"\"<html>foobar</html>\""}`, |
|
||||||
}, |
|
||||||
} |
|
||||||
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) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
|
||||||
} |
|
||||||
@ -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)
|
|
||||||
// <nil>: <nil> (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, "<prefix>", "<indent>") |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
fmt.Println(string(b)) |
|
||||||
// Output:
|
|
||||||
// {
|
|
||||||
// <prefix><indent>"a": 1,
|
|
||||||
// <prefix><indent>"b": 2
|
|
||||||
// <prefix>}
|
|
||||||
} |
|
||||||
|
|
||||||
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":"<b>HTML content</b>"}`)) |
|
||||||
out.WriteTo(os.Stdout) |
|
||||||
// Output:
|
|
||||||
//{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"}
|
|
||||||
} |
|
||||||
@ -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
|
|
||||||
} |
|
||||||
@ -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 |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
@ -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 |
|
||||||
} |
|
||||||
@ -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 |
|
||||||
}{`"<str>"`, `"<str>"`} |
|
||||||
|
|
||||||
// https://golang.org/issue/34154
|
|
||||||
stringOption := struct { |
|
||||||
Bar string `json:"bar,string"` |
|
||||||
}{`<html>foobar</html>`} |
|
||||||
|
|
||||||
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(`"<str>"`), marshalerStruct, |
|
||||||
`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`, |
|
||||||
`{"NonPtr":"<str>","Ptr":"<str>"}`, |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name("stringOption"), stringOption, |
|
||||||
`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`, |
|
||||||
`{"bar":"\"<html>foobar</html>\""}`, |
|
||||||
}, |
|
||||||
} |
|
||||||
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) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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) |
|
||||||
} |
|
||||||
}, |
|
||||||
) |
|
||||||
} |
|
||||||
@ -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 |
||||||
|
} |
||||||
@ -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 |
||||||
|
} |
||||||
Loading…
Reference in new issue