47 changed files with 687 additions and 19133 deletions
@ -0,0 +1,424 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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