implement filter codec

This commit is contained in:
2025-08-26 18:26:34 +01:00
parent c958a7d9ed
commit 1ba2bb0a9b
47 changed files with 687 additions and 19133 deletions

View File

@@ -32,7 +32,8 @@ import (
// same as go 1.25 json v1 except with this one stupidity removed.
type E struct {
// ID is the SHA256 hash of the canonical encoding of the event in binary format
// ID is the SHA256 hash of the canonical encoding of the event in binary
// format
ID []byte
// Pubkey is the public key of the event creator in binary format
@@ -57,8 +58,8 @@ type E struct {
// Pubkey in binary format.
Sig []byte
// b is the decode buffer for the event.E. this is where the UnmarshalJSON will
// source the memory to store all of the fields except for the tags.
// b is the decode buffer for the event.E. this is where the UnmarshalJSON
// will source the memory to store all of the fields except for the tags.
b bufpool.B
}

View File

@@ -89,7 +89,7 @@ func TestExamplesCache(t *testing.T) {
t.Fatalf("failed to re-marshal back original")
}
ev.Free()
// Don't return scanner.Bytes() to the pool as it's not a buffer we own
// Don't return scanner.Bytes() to the pool as it's a buffer from embed.
// bufpool.PutBytes(b)
bufpool.PutBytes(b2)
bufpool.PutBytes(c)

View File

@@ -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
}

View File

@@ -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 = &timestamp.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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"}
}

View File

@@ -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
}

View File

@@ -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)
}
})
}

View File

@@ -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
}
}
})
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
})
}
}

View File

@@ -1,332 +0,0 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build goexperiment.jsonv2
package jsonwire
import (
"bufio"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"flag"
"math"
"net/http"
"reflect"
"strconv"
"strings"
"testing"
"time"
"encoding/json/internal/jsonflags"
)
func TestAppendQuote(t *testing.T) {
tests := []struct {
in string
flags jsonflags.Bools
want string
wantErr error
wantErrUTF8 error
}{
{"", 0, `""`, nil, nil},
{"hello", 0, `"hello"`, nil, nil},
{"\x00", 0, `"\u0000"`, nil, nil},
{"\x1f", 0, `"\u001f"`, nil, nil},
{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0, `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, nil, nil},
{" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", 0, "\" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f\"", nil, nil},
{" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", jsonflags.EscapeForHTML, "\" !#$%\\u0026'()*+,-./0123456789:;\\u003c=\\u003e?@[]^_`{|}~\x7f\"", nil, nil},
{" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", jsonflags.EscapeForJS, "\" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f\"", nil, nil},
{"\u2027\u2028\u2029\u2030", 0, "\"\u2027\u2028\u2029\u2030\"", nil, nil},
{"\u2027\u2028\u2029\u2030", jsonflags.EscapeForHTML, "\"\u2027\u2028\u2029\u2030\"", nil, nil},
{"\u2027\u2028\u2029\u2030", jsonflags.EscapeForJS, "\"\u2027\\u2028\\u2029\u2030\"", nil, nil},
{"x\x80\ufffd", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xff\ufffd", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xc0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8},
{"x\xc0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xe0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8},
{"x\xe0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xe0\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xf0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8},
{"x\xf0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xf0\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xf0\x80\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"x\xed\xba\xad", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8},
{"\"\\/\b\f\n\r\t", 0, `"\"\\/\b\f\n\r\t"`, nil, nil},
{"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).", 0, `"٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)."`, nil, nil},
{"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", 0, "\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", nil, nil},
{"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", 0, "\"\\u0000\\u001f\u0020\\\"\u0026\u003c\u003e\\\\\u007f\u0080\u2028\u2029\ufffd\U0001f602\"", nil, nil},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
var flags jsonflags.Flags
flags.Set(tt.flags | 1)
flags.Set(jsonflags.AllowInvalidUTF8 | 1)
got, gotErr := AppendQuote(nil, tt.in, &flags)
if string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr)
}
flags.Set(jsonflags.AllowInvalidUTF8 | 0)
switch got, gotErr := AppendQuote(nil, tt.in, &flags); {
case tt.wantErrUTF8 == nil && (string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr)):
t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr)
case tt.wantErrUTF8 != nil && (!strings.HasPrefix(tt.want, string(got)) || !reflect.DeepEqual(gotErr, tt.wantErrUTF8)):
t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErrUTF8)
}
})
}
}
func TestAppendNumber(t *testing.T) {
tests := []struct {
in float64
want32 string
want64 string
}{
{math.E, "2.7182817", "2.718281828459045"},
{math.Pi, "3.1415927", "3.141592653589793"},
{math.SmallestNonzeroFloat32, "1e-45", "1.401298464324817e-45"},
{math.SmallestNonzeroFloat64, "0", "5e-324"},
{math.MaxFloat32, "3.4028235e+38", "3.4028234663852886e+38"},
{math.MaxFloat64, "", "1.7976931348623157e+308"},
{0.1111111111111111, "0.11111111", "0.1111111111111111"},
{0.2222222222222222, "0.22222222", "0.2222222222222222"},
{0.3333333333333333, "0.33333334", "0.3333333333333333"},
{0.4444444444444444, "0.44444445", "0.4444444444444444"},
{0.5555555555555555, "0.5555556", "0.5555555555555555"},
{0.6666666666666666, "0.6666667", "0.6666666666666666"},
{0.7777777777777777, "0.7777778", "0.7777777777777777"},
{0.8888888888888888, "0.8888889", "0.8888888888888888"},
{0.9999999999999999, "1", "0.9999999999999999"},
// The following entries are from RFC 8785, appendix B
// which are designed to ensure repeatable formatting of 64-bit floats.
{math.Float64frombits(0x0000000000000000), "0", "0"},
{math.Float64frombits(0x8000000000000000), "-0", "-0"}, // differs from RFC 8785
{math.Float64frombits(0x0000000000000001), "0", "5e-324"},
{math.Float64frombits(0x8000000000000001), "-0", "-5e-324"},
{math.Float64frombits(0x7fefffffffffffff), "", "1.7976931348623157e+308"},
{math.Float64frombits(0xffefffffffffffff), "", "-1.7976931348623157e+308"},
{math.Float64frombits(0x4340000000000000), "9007199000000000", "9007199254740992"},
{math.Float64frombits(0xc340000000000000), "-9007199000000000", "-9007199254740992"},
{math.Float64frombits(0x4430000000000000), "295147900000000000000", "295147905179352830000"},
{math.Float64frombits(0x44b52d02c7e14af5), "1e+23", "9.999999999999997e+22"},
{math.Float64frombits(0x44b52d02c7e14af6), "1e+23", "1e+23"},
{math.Float64frombits(0x44b52d02c7e14af7), "1e+23", "1.0000000000000001e+23"},
{math.Float64frombits(0x444b1ae4d6e2ef4e), "1e+21", "999999999999999700000"},
{math.Float64frombits(0x444b1ae4d6e2ef4f), "1e+21", "999999999999999900000"},
{math.Float64frombits(0x444b1ae4d6e2ef50), "1e+21", "1e+21"},
{math.Float64frombits(0x3eb0c6f7a0b5ed8c), "0.000001", "9.999999999999997e-7"},
{math.Float64frombits(0x3eb0c6f7a0b5ed8d), "0.000001", "0.000001"},
{math.Float64frombits(0x41b3de4355555553), "333333340", "333333333.3333332"},
{math.Float64frombits(0x41b3de4355555554), "333333340", "333333333.33333325"},
{math.Float64frombits(0x41b3de4355555555), "333333340", "333333333.3333333"},
{math.Float64frombits(0x41b3de4355555556), "333333340", "333333333.3333334"},
{math.Float64frombits(0x41b3de4355555557), "333333340", "333333333.33333343"},
{math.Float64frombits(0xbecbf647612f3696), "-0.0000033333333", "-0.0000033333333333333333"},
{math.Float64frombits(0x43143ff3c1cb0959), "1424953900000000", "1424953923781206.2"},
// The following are select entries from RFC 8785, appendix B,
// but modified for equivalent 32-bit behavior.
{float64(math.Float32frombits(0x65a96815)), "9.999999e+22", "9.999998877476383e+22"},
{float64(math.Float32frombits(0x65a96816)), "1e+23", "9.999999778196308e+22"},
{float64(math.Float32frombits(0x65a96817)), "1.0000001e+23", "1.0000000678916234e+23"},
{float64(math.Float32frombits(0x6258d725)), "999999900000000000000", "999999879303389000000"},
{float64(math.Float32frombits(0x6258d726)), "999999950000000000000", "999999949672133200000"},
{float64(math.Float32frombits(0x6258d727)), "1e+21", "1.0000000200408773e+21"},
{float64(math.Float32frombits(0x6258d728)), "1.0000001e+21", "1.0000000904096215e+21"},
{float64(math.Float32frombits(0x358637bc)), "9.999999e-7", "9.99999883788405e-7"},
{float64(math.Float32frombits(0x358637bd)), "0.000001", "9.999999974752427e-7"},
{float64(math.Float32frombits(0x358637be)), "0.0000010000001", "0.0000010000001111620804"},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
if got32 := string(AppendFloat(nil, tt.in, 32)); got32 != tt.want32 && tt.want32 != "" {
t.Errorf("AppendFloat(nil, %v, 32) = %v, want %v", tt.in, got32, tt.want32)
}
if got64 := string(AppendFloat(nil, tt.in, 64)); got64 != tt.want64 && tt.want64 != "" {
t.Errorf("AppendFloat(nil, %v, 64) = %v, want %v", tt.in, got64, tt.want64)
}
})
}
}
// The default of 1e4 lines was chosen since it is sufficiently large to include
// test numbers from all three categories (i.e., static, series, and random).
// Yet, it is sufficiently low to execute quickly relative to other tests.
//
// Processing 1e8 lines takes a minute and processes about 4GiB worth of text.
var testCanonicalNumberLines = flag.Float64("canonical-number-lines", 1e4, "specify the number of lines to check from the canonical numbers testdata")
// TestCanonicalNumber verifies that appendNumber complies with RFC 8785
// according to the testdata provided by the reference implementation.
// See https://github.com/cyberphone/json-canonicalization/tree/master/testdata#es6-numbers.
func TestCanonicalNumber(t *testing.T) {
const testfileURL = "https://github.com/cyberphone/json-canonicalization/releases/download/es6testfile/es6testfile100m.txt.gz"
hashes := map[float64]string{
1e3: "be18b62b6f69cdab33a7e0dae0d9cfa869fda80ddc712221570f9f40a5878687",
1e4: "b9f7a8e75ef22a835685a52ccba7f7d6bdc99e34b010992cbc5864cd12be6892",
1e5: "22776e6d4b49fa294a0d0f349268e5c28808fe7e0cb2bcbe28f63894e494d4c7",
1e6: "49415fee2c56c77864931bd3624faad425c3c577d6d74e89a83bc725506dad16",
1e7: "b9f8a44a91d46813b21b9602e72f112613c91408db0b8341fb94603d9db135e0",
1e8: "0f7dda6b0837dde083c5d6b896f7d62340c8a2415b0c7121d83145e08a755272",
}
wantHash := hashes[*testCanonicalNumberLines]
if wantHash == "" {
t.Fatalf("canonical-number-lines must be one of the following values: 1e3, 1e4, 1e5, 1e6, 1e7, 1e8")
}
numLines := int(*testCanonicalNumberLines)
// generator returns a function that generates the next float64 to format.
// This implements the algorithm specified in the reference implementation.
generator := func() func() float64 {
static := [...]uint64{
0x0000000000000000, 0x8000000000000000, 0x0000000000000001, 0x8000000000000001,
0xc46696695dbd1cc3, 0xc43211ede4974a35, 0xc3fce97ca0f21056, 0xc3c7213080c1a6ac,
0xc39280f39a348556, 0xc35d9b1f5d20d557, 0xc327af4c4a80aaac, 0xc2f2f2a36ecd5556,
0xc2be51057e155558, 0xc28840d131aaaaac, 0xc253670dc1555557, 0xc21f0b4935555557,
0xc1e8d5d42aaaaaac, 0xc1b3de4355555556, 0xc17fca0555555556, 0xc1496e6aaaaaaaab,
0xc114585555555555, 0xc0e046aaaaaaaaab, 0xc0aa0aaaaaaaaaaa, 0xc074d55555555555,
0xc040aaaaaaaaaaab, 0xc00aaaaaaaaaaaab, 0xbfd5555555555555, 0xbfa1111111111111,
0xbf6b4e81b4e81b4f, 0xbf35d867c3ece2a5, 0xbf0179ec9cbd821e, 0xbecbf647612f3696,
0xbe965e9f80f29212, 0xbe61e54c672874db, 0xbe2ca213d840baf8, 0xbdf6e80fe033c8c6,
0xbdc2533fe68fd3d2, 0xbd8d51ffd74c861c, 0xbd5774ccac3d3817, 0xbd22c3d6f030f9ac,
0xbcee0624b3818f79, 0xbcb804ea293472c7, 0xbc833721ba905bd3, 0xbc4ebe9c5db3c61e,
0xbc18987d17c304e5, 0xbbe3ad30dfcf371d, 0xbbaf7b816618582f, 0xbb792f9ab81379bf,
0xbb442615600f9499, 0xbb101e77800c76e1, 0xbad9ca58cce0be35, 0xbaa4a1e0a3e6fe90,
0xba708180831f320d, 0xba3a68cd9e985016, 0x446696695dbd1cc3, 0x443211ede4974a35,
0x43fce97ca0f21056, 0x43c7213080c1a6ac, 0x439280f39a348556, 0x435d9b1f5d20d557,
0x4327af4c4a80aaac, 0x42f2f2a36ecd5556, 0x42be51057e155558, 0x428840d131aaaaac,
0x4253670dc1555557, 0x421f0b4935555557, 0x41e8d5d42aaaaaac, 0x41b3de4355555556,
0x417fca0555555556, 0x41496e6aaaaaaaab, 0x4114585555555555, 0x40e046aaaaaaaaab,
0x40aa0aaaaaaaaaaa, 0x4074d55555555555, 0x4040aaaaaaaaaaab, 0x400aaaaaaaaaaaab,
0x3fd5555555555555, 0x3fa1111111111111, 0x3f6b4e81b4e81b4f, 0x3f35d867c3ece2a5,
0x3f0179ec9cbd821e, 0x3ecbf647612f3696, 0x3e965e9f80f29212, 0x3e61e54c672874db,
0x3e2ca213d840baf8, 0x3df6e80fe033c8c6, 0x3dc2533fe68fd3d2, 0x3d8d51ffd74c861c,
0x3d5774ccac3d3817, 0x3d22c3d6f030f9ac, 0x3cee0624b3818f79, 0x3cb804ea293472c7,
0x3c833721ba905bd3, 0x3c4ebe9c5db3c61e, 0x3c18987d17c304e5, 0x3be3ad30dfcf371d,
0x3baf7b816618582f, 0x3b792f9ab81379bf, 0x3b442615600f9499, 0x3b101e77800c76e1,
0x3ad9ca58cce0be35, 0x3aa4a1e0a3e6fe90, 0x3a708180831f320d, 0x3a3a68cd9e985016,
0x4024000000000000, 0x4014000000000000, 0x3fe0000000000000, 0x3fa999999999999a,
0x3f747ae147ae147b, 0x3f40624dd2f1a9fc, 0x3f0a36e2eb1c432d, 0x3ed4f8b588e368f1,
0x3ea0c6f7a0b5ed8d, 0x3e6ad7f29abcaf48, 0x3e35798ee2308c3a, 0x3ed539223589fa95,
0x3ed4ff26cd5a7781, 0x3ed4f95a762283ff, 0x3ed4f8c60703520c, 0x3ed4f8b72f19cd0d,
0x3ed4f8b5b31c0c8d, 0x3ed4f8b58d1c461a, 0x3ed4f8b5894f7f0e, 0x3ed4f8b588ee37f3,
0x3ed4f8b588e47da4, 0x3ed4f8b588e3849c, 0x3ed4f8b588e36bb5, 0x3ed4f8b588e36937,
0x3ed4f8b588e368f8, 0x3ed4f8b588e368f1, 0x3ff0000000000000, 0xbff0000000000000,
0xbfeffffffffffffa, 0xbfeffffffffffffb, 0x3feffffffffffffa, 0x3feffffffffffffb,
0x3feffffffffffffc, 0x3feffffffffffffe, 0xbfefffffffffffff, 0xbfefffffffffffff,
0x3fefffffffffffff, 0x3fefffffffffffff, 0x3fd3333333333332, 0x3fd3333333333333,
0x3fd3333333333334, 0x0010000000000000, 0x000ffffffffffffd, 0x000fffffffffffff,
0x7fefffffffffffff, 0xffefffffffffffff, 0x4340000000000000, 0xc340000000000000,
0x4430000000000000, 0x44b52d02c7e14af5, 0x44b52d02c7e14af6, 0x44b52d02c7e14af7,
0x444b1ae4d6e2ef4e, 0x444b1ae4d6e2ef4f, 0x444b1ae4d6e2ef50, 0x3eb0c6f7a0b5ed8c,
0x3eb0c6f7a0b5ed8d, 0x41b3de4355555553, 0x41b3de4355555554, 0x41b3de4355555555,
0x41b3de4355555556, 0x41b3de4355555557, 0xbecbf647612f3696, 0x43143ff3c1cb0959,
}
var state struct {
idx int
data []byte
block [sha256.Size]byte
}
return func() float64 {
const numSerial = 2000
var f float64
switch {
case state.idx < len(static):
f = math.Float64frombits(static[state.idx])
case state.idx < len(static)+numSerial:
f = math.Float64frombits(0x0010000000000000 + uint64(state.idx-len(static)))
default:
for f == 0 || math.IsNaN(f) || math.IsInf(f, 0) {
if len(state.data) == 0 {
state.block = sha256.Sum256(state.block[:])
state.data = state.block[:]
}
f = math.Float64frombits(binary.LittleEndian.Uint64(state.data))
state.data = state.data[8:]
}
}
state.idx++
return f
}
}
// Pass through the test twice. In the first pass we only hash the output,
// while in the second pass we check every line against the golden testdata.
// If the hashes match in the first pass, then we skip the second pass.
for _, checkGolden := range []bool{false, true} {
var br *bufio.Reader // for line-by-line reading of es6testfile100m.txt
if checkGolden {
resp, err := http.Get(testfileURL)
if err != nil {
t.Fatalf("http.Get error: %v", err)
}
defer resp.Body.Close()
zr, err := gzip.NewReader(resp.Body)
if err != nil {
t.Fatalf("gzip.NewReader error: %v", err)
}
br = bufio.NewReader(zr)
}
// appendNumberJCS differs from appendNumber only for -0.
appendNumberJCS := func(b []byte, f float64) []byte {
if math.Signbit(f) && f == 0 {
return append(b, '0')
}
return AppendFloat(b, f, 64)
}
var gotLine []byte
next := generator()
hash := sha256.New()
start := time.Now()
lastPrint := start
for n := 1; n <= numLines; n++ {
// Generate the formatted line for this number.
f := next()
gotLine = gotLine[:0] // reset from previous usage
gotLine = strconv.AppendUint(gotLine, math.Float64bits(f), 16)
gotLine = append(gotLine, ',')
gotLine = appendNumberJCS(gotLine, f)
gotLine = append(gotLine, '\n')
hash.Write(gotLine)
// Check that the formatted line matches.
if checkGolden {
wantLine, err := br.ReadBytes('\n')
if err != nil {
t.Fatalf("bufio.Reader.ReadBytes error: %v", err)
}
if !bytes.Equal(gotLine, wantLine) {
t.Errorf("mismatch on line %d:\n\tgot %v\n\twant %v",
n, strings.TrimSpace(string(gotLine)), strings.TrimSpace(string(wantLine)))
}
}
// Print progress.
if now := time.Now(); now.Sub(lastPrint) > time.Second || n == numLines {
remaining := float64(now.Sub(start)) * float64(numLines-n) / float64(n)
t.Logf("%0.3f%% (%v remaining)",
100.0*float64(n)/float64(numLines),
time.Duration(remaining).Round(time.Second))
lastPrint = now
}
}
gotHash := hex.EncodeToString(hash.Sum(nil))
if gotHash == wantHash {
return // hashes match, no need to check golden testdata
}
}
}

View File

@@ -1,98 +0,0 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build goexperiment.jsonv2
package jsonwire
import (
"cmp"
"slices"
"testing"
"unicode/utf16"
"unicode/utf8"
)
func TestQuoteRune(t *testing.T) {
tests := []struct{ in, want string }{
{"x", `'x'`},
{"\n", `'\n'`},
{"'", `'\''`},
{"\xff", `'\xff'`},
{"💩", `'💩'`},
{"💩"[:1], `'\xf0'`},
{"\uffff", `'\uffff'`},
{"\U00101234", `'\U00101234'`},
}
for _, tt := range tests {
got := QuoteRune([]byte(tt.in))
if got != tt.want {
t.Errorf("quoteRune(%q) = %s, want %s", tt.in, got, tt.want)
}
}
}
var compareUTF16Testdata = []string{"", "\r", "1", "f\xfe", "f\xfe\xff", "f\xff", "\u0080", "\u00f6", "\u20ac", "\U0001f600", "\ufb33"}
func TestCompareUTF16(t *testing.T) {
for i, si := range compareUTF16Testdata {
for j, sj := range compareUTF16Testdata {
got := CompareUTF16([]byte(si), []byte(sj))
want := cmp.Compare(i, j)
if got != want {
t.Errorf("CompareUTF16(%q, %q) = %v, want %v", si, sj, got, want)
}
}
}
}
func FuzzCompareUTF16(f *testing.F) {
for _, td1 := range compareUTF16Testdata {
for _, td2 := range compareUTF16Testdata {
f.Add([]byte(td1), []byte(td2))
}
}
// CompareUTF16Simple is identical to CompareUTF16,
// but relies on naively converting a string to a []uint16 codepoints.
// It is easy to verify as correct, but is slow.
CompareUTF16Simple := func(x, y []byte) int {
ux := utf16.Encode([]rune(string(x)))
uy := utf16.Encode([]rune(string(y)))
return slices.Compare(ux, uy)
}
f.Fuzz(func(t *testing.T, s1, s2 []byte) {
// Compare the optimized and simplified implementations.
got := CompareUTF16(s1, s2)
want := CompareUTF16Simple(s1, s2)
if got != want && utf8.Valid(s1) && utf8.Valid(s2) {
t.Errorf("CompareUTF16(%q, %q) = %v, want %v", s1, s2, got, want)
}
})
}
func TestTruncatePointer(t *testing.T) {
tests := []struct{ in, want string }{
{"hello", "hello"},
{"/a/b/c", "/a/b/c"},
{"/a/b/c/d/e/f/g", "/a/b/…/f/g"},
{"supercalifragilisticexpialidocious", "super…cious"},
{"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/supe…/…cious"},
{"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/supe…/…/…cious"},
{"/a/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/a/…/…cious"},
{"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious/b", "/supe…/…/b"},
{"/fizz/buzz/bazz", "/fizz/…/bazz"},
{"/fizz/buzz/bazz/razz", "/fizz/…/razz"},
{"/////////////////////////////", "/////…/////"},
{"/🎄❤️✨/🎁✅😊/🎅🔥⭐", "/🎄…/…/…⭐"},
}
for _, tt := range tests {
got := TruncatePointer(tt.in, 10)
if got != tt.want {
t.Errorf("TruncatePointer(%q) = %q, want %q", tt.in, got, tt.want)
}
}
}

View File

@@ -1,856 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build goexperiment.jsonv2
package jsontext
import (
"bytes"
"errors"
"io"
"math"
"math/rand"
"path"
"reflect"
"strings"
"testing"
"encoding/json/internal/jsontest"
"encoding/json/internal/jsonwire"
)
func E(err error) *SyntacticError {
return &SyntacticError{Err: err}
}
func newInvalidCharacterError(prefix, where string) *SyntacticError {
return E(jsonwire.NewInvalidCharacterError(prefix, where))
}
func newInvalidEscapeSequenceError(what string) *SyntacticError {
return E(jsonwire.NewInvalidEscapeSequenceError(what))
}
func (e *SyntacticError) withPos(prefix string, pointer Pointer) *SyntacticError {
e.ByteOffset = int64(len(prefix))
e.JSONPointer = pointer
return e
}
func equalError(x, y error) bool {
return reflect.DeepEqual(x, y)
}
var (
zeroToken Token
zeroValue Value
)
// tokOrVal is either a Token or a Value.
type tokOrVal interface{ Kind() Kind }
type coderTestdataEntry struct {
name jsontest.CaseName
in string
outCompacted string
outEscaped string // outCompacted if empty; escapes all runes in a string
outIndented string // outCompacted if empty; uses " " for indent prefix and "\t" for indent
outCanonicalized string // outCompacted if empty
tokens []Token
pointers []Pointer
}
var coderTestdata = []coderTestdataEntry{{
name: jsontest.Name("Null"),
in: ` null `,
outCompacted: `null`,
tokens: []Token{Null},
pointers: []Pointer{""},
}, {
name: jsontest.Name("False"),
in: ` false `,
outCompacted: `false`,
tokens: []Token{False},
}, {
name: jsontest.Name("True"),
in: ` true `,
outCompacted: `true`,
tokens: []Token{True},
}, {
name: jsontest.Name("EmptyString"),
in: ` "" `,
outCompacted: `""`,
tokens: []Token{String("")},
}, {
name: jsontest.Name("SimpleString"),
in: ` "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" `,
outCompacted: `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"`,
outEscaped: `"\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a"`,
tokens: []Token{String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")},
}, {
name: jsontest.Name("ComplicatedString"),
in: " \"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + ` \ud800\udead \"\\\/\b\f\n\r\t \u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009" `,
outCompacted: "\"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + " 𐊭 \\\"\\\\/\\b\\f\\n\\r\\t \\\"\\\\/\\b\\f\\n\\r\\t\"",
outEscaped: `"\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u4e16\u754c\u0020\ud83c\udf1f\u2605\u2606\u2729\ud83c\udf20\u0020\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02\u0020\ud800\udead\u0020\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009\u0020\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`,
outCanonicalized: `"Hello, 世界 🌟★☆✩🌠 €ö€힙דּ<EE8080>😂 𐊭 \"\\/\b\f\n\r\t \"\\/\b\f\n\r\t"`,
tokens: []Token{rawToken("\"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + " 𐊭 \\\"\\\\/\\b\\f\\n\\r\\t \\\"\\\\/\\b\\f\\n\\r\\t\"")},
}, {
name: jsontest.Name("ZeroNumber"),
in: ` 0 `,
outCompacted: `0`,
tokens: []Token{Uint(0)},
}, {
name: jsontest.Name("SimpleNumber"),
in: ` 123456789 `,
outCompacted: `123456789`,
tokens: []Token{Uint(123456789)},
}, {
name: jsontest.Name("NegativeNumber"),
in: ` -123456789 `,
outCompacted: `-123456789`,
tokens: []Token{Int(-123456789)},
}, {
name: jsontest.Name("FractionalNumber"),
in: " 0.123456789 ",
outCompacted: `0.123456789`,
tokens: []Token{Float(0.123456789)},
}, {
name: jsontest.Name("ExponentNumber"),
in: " 0e12456789 ",
outCompacted: `0e12456789`,
outCanonicalized: `0`,
tokens: []Token{rawToken(`0e12456789`)},
}, {
name: jsontest.Name("ExponentNumberP"),
in: " 0e+12456789 ",
outCompacted: `0e+12456789`,
outCanonicalized: `0`,
tokens: []Token{rawToken(`0e+12456789`)},
}, {
name: jsontest.Name("ExponentNumberN"),
in: " 0e-12456789 ",
outCompacted: `0e-12456789`,
outCanonicalized: `0`,
tokens: []Token{rawToken(`0e-12456789`)},
}, {
name: jsontest.Name("ComplicatedNumber"),
in: ` -123456789.987654321E+0123456789 `,
outCompacted: `-123456789.987654321E+0123456789`,
outCanonicalized: `-1.7976931348623157e+308`,
tokens: []Token{rawToken(`-123456789.987654321E+0123456789`)},
}, {
name: jsontest.Name("Numbers"),
in: ` [
0, -0, 0.0, -0.0, 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, 1e1000,
-5e-324, 1e+100, 1.7976931348623157e+308,
9007199254740990, 9007199254740991, 9007199254740992, 9007199254740993, 9007199254740994,
-9223372036854775808, 9223372036854775807, 0, 18446744073709551615
] `,
outCompacted: "[0,-0,0.0,-0.0,1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001,1e1000,-5e-324,1e+100,1.7976931348623157e+308,9007199254740990,9007199254740991,9007199254740992,9007199254740993,9007199254740994,-9223372036854775808,9223372036854775807,0,18446744073709551615]",
outIndented: `[
0,
-0,
0.0,
-0.0,
1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001,
1e1000,
-5e-324,
1e+100,
1.7976931348623157e+308,
9007199254740990,
9007199254740991,
9007199254740992,
9007199254740993,
9007199254740994,
-9223372036854775808,
9223372036854775807,
0,
18446744073709551615
]`,
outCanonicalized: `[0,0,0,0,1,1.7976931348623157e+308,-5e-324,1e+100,1.7976931348623157e+308,9007199254740990,9007199254740991,9007199254740992,9007199254740992,9007199254740994,-9223372036854776000,9223372036854776000,0,18446744073709552000]`,
tokens: []Token{
BeginArray,
Float(0), Float(math.Copysign(0, -1)), rawToken(`0.0`), rawToken(`-0.0`), rawToken(`1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001`), rawToken(`1e1000`),
Float(-5e-324), Float(1e100), Float(1.7976931348623157e+308),
Float(9007199254740990), Float(9007199254740991), Float(9007199254740992), rawToken(`9007199254740993`), rawToken(`9007199254740994`),
Int(minInt64), Int(maxInt64), Uint(minUint64), Uint(maxUint64),
EndArray,
},
pointers: []Pointer{
"", "/0", "/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/10", "/11", "/12", "/13", "/14", "/15", "/16", "/17", "",
},
}, {
name: jsontest.Name("ObjectN0"),
in: ` { } `,
outCompacted: `{}`,
tokens: []Token{BeginObject, EndObject},
pointers: []Pointer{"", ""},
}, {
name: jsontest.Name("ObjectN1"),
in: ` { "0" : 0 } `,
outCompacted: `{"0":0}`,
outEscaped: `{"\u0030":0}`,
outIndented: `{
"0": 0
}`,
tokens: []Token{BeginObject, String("0"), Uint(0), EndObject},
pointers: []Pointer{"", "/0", "/0", ""},
}, {
name: jsontest.Name("ObjectN2"),
in: ` { "0" : 0 , "1" : 1 } `,
outCompacted: `{"0":0,"1":1}`,
outEscaped: `{"\u0030":0,"\u0031":1}`,
outIndented: `{
"0": 0,
"1": 1
}`,
tokens: []Token{BeginObject, String("0"), Uint(0), String("1"), Uint(1), EndObject},
pointers: []Pointer{"", "/0", "/0", "/1", "/1", ""},
}, {
name: jsontest.Name("ObjectNested"),
in: ` { "0" : { "1" : { "2" : { "3" : { "4" : { } } } } } } `,
outCompacted: `{"0":{"1":{"2":{"3":{"4":{}}}}}}`,
outEscaped: `{"\u0030":{"\u0031":{"\u0032":{"\u0033":{"\u0034":{}}}}}}`,
outIndented: `{
"0": {
"1": {
"2": {
"3": {
"4": {}
}
}
}
}
}`,
tokens: []Token{BeginObject, String("0"), BeginObject, String("1"), BeginObject, String("2"), BeginObject, String("3"), BeginObject, String("4"), BeginObject, EndObject, EndObject, EndObject, EndObject, EndObject, EndObject},
pointers: []Pointer{
"",
"/0", "/0",
"/0/1", "/0/1",
"/0/1/2", "/0/1/2",
"/0/1/2/3", "/0/1/2/3",
"/0/1/2/3/4", "/0/1/2/3/4",
"/0/1/2/3/4",
"/0/1/2/3",
"/0/1/2",
"/0/1",
"/0",
"",
},
}, {
name: jsontest.Name("ObjectSuperNested"),
in: `{"": {
"44444": {
"6666666": "ccccccc",
"77777777": "bb",
"555555": "aaaa"
},
"0": {
"3333": "bbb",
"11": "",
"222": "aaaaa"
}
}}`,
outCompacted: `{"":{"44444":{"6666666":"ccccccc","77777777":"bb","555555":"aaaa"},"0":{"3333":"bbb","11":"","222":"aaaaa"}}}`,
outEscaped: `{"":{"\u0034\u0034\u0034\u0034\u0034":{"\u0036\u0036\u0036\u0036\u0036\u0036\u0036":"\u0063\u0063\u0063\u0063\u0063\u0063\u0063","\u0037\u0037\u0037\u0037\u0037\u0037\u0037\u0037":"\u0062\u0062","\u0035\u0035\u0035\u0035\u0035\u0035":"\u0061\u0061\u0061\u0061"},"\u0030":{"\u0033\u0033\u0033\u0033":"\u0062\u0062\u0062","\u0031\u0031":"","\u0032\u0032\u0032":"\u0061\u0061\u0061\u0061\u0061"}}}`,
outIndented: `{
"": {
"44444": {
"6666666": "ccccccc",
"77777777": "bb",
"555555": "aaaa"
},
"0": {
"3333": "bbb",
"11": "",
"222": "aaaaa"
}
}
}`,
outCanonicalized: `{"":{"0":{"11":"","222":"aaaaa","3333":"bbb"},"44444":{"555555":"aaaa","6666666":"ccccccc","77777777":"bb"}}}`,
tokens: []Token{
BeginObject,
String(""),
BeginObject,
String("44444"),
BeginObject,
String("6666666"), String("ccccccc"),
String("77777777"), String("bb"),
String("555555"), String("aaaa"),
EndObject,
String("0"),
BeginObject,
String("3333"), String("bbb"),
String("11"), String(""),
String("222"), String("aaaaa"),
EndObject,
EndObject,
EndObject,
},
pointers: []Pointer{
"",
"/", "/",
"//44444", "//44444",
"//44444/6666666", "//44444/6666666",
"//44444/77777777", "//44444/77777777",
"//44444/555555", "//44444/555555",
"//44444",
"//0", "//0",
"//0/3333", "//0/3333",
"//0/11", "//0/11",
"//0/222", "//0/222",
"//0",
"/",
"",
},
}, {
name: jsontest.Name("ArrayN0"),
in: ` [ ] `,
outCompacted: `[]`,
tokens: []Token{BeginArray, EndArray},
pointers: []Pointer{"", ""},
}, {
name: jsontest.Name("ArrayN1"),
in: ` [ 0 ] `,
outCompacted: `[0]`,
outIndented: `[
0
]`,
tokens: []Token{BeginArray, Uint(0), EndArray},
pointers: []Pointer{"", "/0", ""},
}, {
name: jsontest.Name("ArrayN2"),
in: ` [ 0 , 1 ] `,
outCompacted: `[0,1]`,
outIndented: `[
0,
1
]`,
tokens: []Token{BeginArray, Uint(0), Uint(1), EndArray},
}, {
name: jsontest.Name("ArrayNested"),
in: ` [ [ [ [ [ ] ] ] ] ] `,
outCompacted: `[[[[[]]]]]`,
outIndented: `[
[
[
[
[]
]
]
]
]`,
tokens: []Token{BeginArray, BeginArray, BeginArray, BeginArray, BeginArray, EndArray, EndArray, EndArray, EndArray, EndArray},
pointers: []Pointer{
"",
"/0",
"/0/0",
"/0/0/0",
"/0/0/0/0",
"/0/0/0/0",
"/0/0/0",
"/0/0",
"/0",
"",
},
}, {
name: jsontest.Name("Everything"),
in: ` {
"literals" : [ null , false , true ],
"string" : "Hello, 世界" ,
"number" : 3.14159 ,
"arrayN0" : [ ] ,
"arrayN1" : [ 0 ] ,
"arrayN2" : [ 0 , 1 ] ,
"objectN0" : { } ,
"objectN1" : { "0" : 0 } ,
"objectN2" : { "0" : 0 , "1" : 1 }
} `,
outCompacted: `{"literals":[null,false,true],"string":"Hello, 世界","number":3.14159,"arrayN0":[],"arrayN1":[0],"arrayN2":[0,1],"objectN0":{},"objectN1":{"0":0},"objectN2":{"0":0,"1":1}}`,
outEscaped: `{"\u006c\u0069\u0074\u0065\u0072\u0061\u006c\u0073":[null,false,true],"\u0073\u0074\u0072\u0069\u006e\u0067":"\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u4e16\u754c","\u006e\u0075\u006d\u0062\u0065\u0072":3.14159,"\u0061\u0072\u0072\u0061\u0079\u004e\u0030":[],"\u0061\u0072\u0072\u0061\u0079\u004e\u0031":[0],"\u0061\u0072\u0072\u0061\u0079\u004e\u0032":[0,1],"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0030":{},"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0031":{"\u0030":0},"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0032":{"\u0030":0,"\u0031":1}}`,
outIndented: `{
"literals": [
null,
false,
true
],
"string": "Hello, 世界",
"number": 3.14159,
"arrayN0": [],
"arrayN1": [
0
],
"arrayN2": [
0,
1
],
"objectN0": {},
"objectN1": {
"0": 0
},
"objectN2": {
"0": 0,
"1": 1
}
}`,
outCanonicalized: `{"arrayN0":[],"arrayN1":[0],"arrayN2":[0,1],"literals":[null,false,true],"number":3.14159,"objectN0":{},"objectN1":{"0":0},"objectN2":{"0":0,"1":1},"string":"Hello, 世界"}`,
tokens: []Token{
BeginObject,
String("literals"), BeginArray, Null, False, True, EndArray,
String("string"), String("Hello, 世界"),
String("number"), Float(3.14159),
String("arrayN0"), BeginArray, EndArray,
String("arrayN1"), BeginArray, Uint(0), EndArray,
String("arrayN2"), BeginArray, Uint(0), Uint(1), EndArray,
String("objectN0"), BeginObject, EndObject,
String("objectN1"), BeginObject, String("0"), Uint(0), EndObject,
String("objectN2"), BeginObject, String("0"), Uint(0), String("1"), Uint(1), EndObject,
EndObject,
},
pointers: []Pointer{
"",
"/literals", "/literals",
"/literals/0",
"/literals/1",
"/literals/2",
"/literals",
"/string", "/string",
"/number", "/number",
"/arrayN0", "/arrayN0", "/arrayN0",
"/arrayN1", "/arrayN1",
"/arrayN1/0",
"/arrayN1",
"/arrayN2", "/arrayN2",
"/arrayN2/0",
"/arrayN2/1",
"/arrayN2",
"/objectN0", "/objectN0", "/objectN0",
"/objectN1", "/objectN1",
"/objectN1/0", "/objectN1/0",
"/objectN1",
"/objectN2", "/objectN2",
"/objectN2/0", "/objectN2/0",
"/objectN2/1", "/objectN2/1",
"/objectN2",
"",
},
}}
// TestCoderInterleaved tests that we can interleave calls that operate on
// tokens and raw values. The only error condition is trying to operate on a
// raw value when the next token is an end of object or array.
func TestCoderInterleaved(t *testing.T) {
for _, td := range coderTestdata {
// In TokenFirst and ValueFirst, alternate between tokens and values.
// In TokenDelims, only use tokens for object and array delimiters.
for _, modeName := range []string{"TokenFirst", "ValueFirst", "TokenDelims"} {
t.Run(path.Join(td.name.Name, modeName), func(t *testing.T) {
testCoderInterleaved(t, td.name.Where, modeName, td)
})
}
}
}
func testCoderInterleaved(t *testing.T, where jsontest.CasePos, modeName string, td coderTestdataEntry) {
src := strings.NewReader(td.in)
dst := new(bytes.Buffer)
dec := NewDecoder(src)
enc := NewEncoder(dst)
tickTock := modeName == "TokenFirst"
for {
if modeName == "TokenDelims" {
switch dec.PeekKind() {
case '{', '}', '[', ']':
tickTock = true // as token
default:
tickTock = false // as value
}
}
if tickTock {
tok, err := dec.ReadToken()
if err != nil {
if err == io.EOF {
break
}
t.Fatalf("%s: Decoder.ReadToken error: %v", where, err)
}
if err := enc.WriteToken(tok); err != nil {
t.Fatalf("%s: Encoder.WriteToken error: %v", where, err)
}
} else {
val, err := dec.ReadValue()
if err != nil {
// It is a syntactic error to call ReadValue
// at the end of an object or array.
// Retry as a ReadToken call.
expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']'
if expectError {
if !errors.As(err, new(*SyntacticError)) {
t.Fatalf("%s: Decoder.ReadToken error is %T, want %T", where, err, new(SyntacticError))
}
tickTock = !tickTock
continue
}
if err == io.EOF {
break
}
t.Fatalf("%s: Decoder.ReadValue error: %v", where, err)
}
if err := enc.WriteValue(val); err != nil {
t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
}
}
tickTock = !tickTock
}
got := dst.String()
want := td.outCompacted + "\n"
if got != want {
t.Fatalf("%s: output mismatch:\ngot %q\nwant %q", where, got, want)
}
}
func TestCoderStackPointer(t *testing.T) {
tests := []struct {
token Token
want Pointer
}{
{Null, ""},
{BeginArray, ""},
{EndArray, ""},
{BeginArray, ""},
{Bool(true), "/0"},
{EndArray, ""},
{BeginArray, ""},
{String("hello"), "/0"},
{String("goodbye"), "/1"},
{EndArray, ""},
{BeginObject, ""},
{EndObject, ""},
{BeginObject, ""},
{String("hello"), "/hello"},
{String("goodbye"), "/hello"},
{EndObject, ""},
{BeginObject, ""},
{String(""), "/"},
{Null, "/"},
{String("0"), "/0"},
{Null, "/0"},
{String("~"), "/~0"},
{Null, "/~0"},
{String("/"), "/~1"},
{Null, "/~1"},
{String("a//b~/c/~d~~e"), "/a~1~1b~0~1c~1~0d~0~0e"},
{Null, "/a~1~1b~0~1c~1~0d~0~0e"},
{String(" \r\n\t"), "/ \r\n\t"},
{Null, "/ \r\n\t"},
{EndObject, ""},
{BeginArray, ""},
{BeginObject, "/0"},
{String(""), "/0/"},
{BeginArray, "/0/"},
{BeginObject, "/0//0"},
{String("#"), "/0//0/#"},
{Null, "/0//0/#"},
{EndObject, "/0//0"},
{EndArray, "/0/"},
{EndObject, "/0"},
{EndArray, ""},
}
for _, allowDupes := range []bool{false, true} {
var name string
switch allowDupes {
case false:
name = "RejectDuplicateNames"
case true:
name = "AllowDuplicateNames"
}
t.Run(name, func(t *testing.T) {
bb := new(bytes.Buffer)
enc := NewEncoder(bb, AllowDuplicateNames(allowDupes))
for i, tt := range tests {
if err := enc.WriteToken(tt.token); err != nil {
t.Fatalf("%d: Encoder.WriteToken error: %v", i, err)
}
if got := enc.StackPointer(); got != tests[i].want {
t.Fatalf("%d: Encoder.StackPointer = %v, want %v", i, got, tests[i].want)
}
}
dec := NewDecoder(bb, AllowDuplicateNames(allowDupes))
for i := range tests {
if _, err := dec.ReadToken(); err != nil {
t.Fatalf("%d: Decoder.ReadToken error: %v", i, err)
}
if got := dec.StackPointer(); got != tests[i].want {
t.Fatalf("%d: Decoder.StackPointer = %v, want %v", i, got, tests[i].want)
}
}
})
}
}
func TestCoderMaxDepth(t *testing.T) {
trimArray := func(b []byte) []byte { return b[len(`[`) : len(b)-len(`]`)] }
maxArrays := []byte(strings.Repeat(`[`, maxNestingDepth+1) + strings.Repeat(`]`, maxNestingDepth+1))
trimObject := func(b []byte) []byte { return b[len(`{"":`) : len(b)-len(`}`)] }
maxObjects := []byte(strings.Repeat(`{"":`, maxNestingDepth+1) + `""` + strings.Repeat(`}`, maxNestingDepth+1))
t.Run("Decoder", func(t *testing.T) {
var dec Decoder
checkReadToken := func(t *testing.T, wantKind Kind, wantErr error) {
t.Helper()
if tok, err := dec.ReadToken(); tok.Kind() != wantKind || !equalError(err, wantErr) {
t.Fatalf("Decoder.ReadToken = (%q, %v), want (%q, %v)", byte(tok.Kind()), err, byte(wantKind), wantErr)
}
}
checkReadValue := func(t *testing.T, wantLen int, wantErr error) {
t.Helper()
if val, err := dec.ReadValue(); len(val) != wantLen || !equalError(err, wantErr) {
t.Fatalf("Decoder.ReadValue = (%d, %v), want (%d, %v)", len(val), err, wantLen, wantErr)
}
}
t.Run("ArraysValid/SingleValue", func(t *testing.T) {
dec.s.reset(trimArray(maxArrays), nil)
checkReadValue(t, maxNestingDepth*len(`[]`), nil)
})
t.Run("ArraysValid/TokenThenValue", func(t *testing.T) {
dec.s.reset(trimArray(maxArrays), nil)
checkReadToken(t, '[', nil)
checkReadValue(t, (maxNestingDepth-1)*len(`[]`), nil)
checkReadToken(t, ']', nil)
})
t.Run("ArraysValid/AllTokens", func(t *testing.T) {
dec.s.reset(trimArray(maxArrays), nil)
for range maxNestingDepth {
checkReadToken(t, '[', nil)
}
for range maxNestingDepth {
checkReadToken(t, ']', nil)
}
})
wantErr := &SyntacticError{
ByteOffset: maxNestingDepth,
JSONPointer: Pointer(strings.Repeat("/0", maxNestingDepth)),
Err: errMaxDepth,
}
t.Run("ArraysInvalid/SingleValue", func(t *testing.T) {
dec.s.reset(maxArrays, nil)
checkReadValue(t, 0, wantErr)
})
t.Run("ArraysInvalid/TokenThenValue", func(t *testing.T) {
dec.s.reset(maxArrays, nil)
checkReadToken(t, '[', nil)
checkReadValue(t, 0, wantErr)
})
t.Run("ArraysInvalid/AllTokens", func(t *testing.T) {
dec.s.reset(maxArrays, nil)
for range maxNestingDepth {
checkReadToken(t, '[', nil)
}
checkReadValue(t, 0, wantErr)
})
t.Run("ObjectsValid/SingleValue", func(t *testing.T) {
dec.s.reset(trimObject(maxObjects), nil)
checkReadValue(t, maxNestingDepth*len(`{"":}`)+len(`""`), nil)
})
t.Run("ObjectsValid/TokenThenValue", func(t *testing.T) {
dec.s.reset(trimObject(maxObjects), nil)
checkReadToken(t, '{', nil)
checkReadToken(t, '"', nil)
checkReadValue(t, (maxNestingDepth-1)*len(`{"":}`)+len(`""`), nil)
checkReadToken(t, '}', nil)
})
t.Run("ObjectsValid/AllTokens", func(t *testing.T) {
dec.s.reset(trimObject(maxObjects), nil)
for range maxNestingDepth {
checkReadToken(t, '{', nil)
checkReadToken(t, '"', nil)
}
checkReadToken(t, '"', nil)
for range maxNestingDepth {
checkReadToken(t, '}', nil)
}
})
wantErr = &SyntacticError{
ByteOffset: maxNestingDepth * int64(len(`{"":`)),
JSONPointer: Pointer(strings.Repeat("/", maxNestingDepth)),
Err: errMaxDepth,
}
t.Run("ObjectsInvalid/SingleValue", func(t *testing.T) {
dec.s.reset(maxObjects, nil)
checkReadValue(t, 0, wantErr)
})
t.Run("ObjectsInvalid/TokenThenValue", func(t *testing.T) {
dec.s.reset(maxObjects, nil)
checkReadToken(t, '{', nil)
checkReadToken(t, '"', nil)
checkReadValue(t, 0, wantErr)
})
t.Run("ObjectsInvalid/AllTokens", func(t *testing.T) {
dec.s.reset(maxObjects, nil)
for range maxNestingDepth {
checkReadToken(t, '{', nil)
checkReadToken(t, '"', nil)
}
checkReadToken(t, 0, wantErr)
})
})
t.Run("Encoder", func(t *testing.T) {
var enc Encoder
checkWriteToken := func(t *testing.T, tok Token, wantErr error) {
t.Helper()
if err := enc.WriteToken(tok); !equalError(err, wantErr) {
t.Fatalf("Encoder.WriteToken = %v, want %v", err, wantErr)
}
}
checkWriteValue := func(t *testing.T, val Value, wantErr error) {
t.Helper()
if err := enc.WriteValue(val); !equalError(err, wantErr) {
t.Fatalf("Encoder.WriteValue = %v, want %v", err, wantErr)
}
}
wantErr := &SyntacticError{
ByteOffset: maxNestingDepth,
JSONPointer: Pointer(strings.Repeat("/0", maxNestingDepth)),
Err: errMaxDepth,
}
t.Run("Arrays/SingleValue", func(t *testing.T) {
enc.s.reset(enc.s.Buf[:0], nil)
checkWriteValue(t, maxArrays, wantErr)
checkWriteValue(t, trimArray(maxArrays), nil)
})
t.Run("Arrays/TokenThenValue", func(t *testing.T) {
enc.s.reset(enc.s.Buf[:0], nil)
checkWriteToken(t, BeginArray, nil)
checkWriteValue(t, trimArray(maxArrays), wantErr)
checkWriteValue(t, trimArray(trimArray(maxArrays)), nil)
checkWriteToken(t, EndArray, nil)
})
t.Run("Arrays/AllTokens", func(t *testing.T) {
enc.s.reset(enc.s.Buf[:0], nil)
for range maxNestingDepth {
checkWriteToken(t, BeginArray, nil)
}
checkWriteToken(t, BeginArray, wantErr)
for range maxNestingDepth {
checkWriteToken(t, EndArray, nil)
}
})
wantErr = &SyntacticError{
ByteOffset: maxNestingDepth * int64(len(`{"":`)),
JSONPointer: Pointer(strings.Repeat("/", maxNestingDepth)),
Err: errMaxDepth,
}
t.Run("Objects/SingleValue", func(t *testing.T) {
enc.s.reset(enc.s.Buf[:0], nil)
checkWriteValue(t, maxObjects, wantErr)
checkWriteValue(t, trimObject(maxObjects), nil)
})
t.Run("Objects/TokenThenValue", func(t *testing.T) {
enc.s.reset(enc.s.Buf[:0], nil)
checkWriteToken(t, BeginObject, nil)
checkWriteToken(t, String(""), nil)
checkWriteValue(t, trimObject(maxObjects), wantErr)
checkWriteValue(t, trimObject(trimObject(maxObjects)), nil)
checkWriteToken(t, EndObject, nil)
})
t.Run("Objects/AllTokens", func(t *testing.T) {
enc.s.reset(enc.s.Buf[:0], nil)
for range maxNestingDepth - 1 {
checkWriteToken(t, BeginObject, nil)
checkWriteToken(t, String(""), nil)
}
checkWriteToken(t, BeginObject, nil)
checkWriteToken(t, String(""), nil)
checkWriteToken(t, BeginObject, wantErr)
checkWriteToken(t, String(""), nil)
for range maxNestingDepth {
checkWriteToken(t, EndObject, nil)
}
})
})
}
// FaultyBuffer implements io.Reader and io.Writer.
// It may process fewer bytes than the provided buffer
// and may randomly return an error.
type FaultyBuffer struct {
B []byte
// MaxBytes is the maximum number of bytes read/written.
// A random number of bytes within [0, MaxBytes] are processed.
// A non-positive value is treated as infinity.
MaxBytes int
// MayError specifies whether to randomly provide this error.
// Even if an error is returned, no bytes are dropped.
MayError error
// Rand to use for pseudo-random behavior.
// If nil, it will be initialized with rand.NewSource(0).
Rand rand.Source
}
func (p *FaultyBuffer) Read(b []byte) (int, error) {
b = b[:copy(b[:p.mayTruncate(len(b))], p.B)]
p.B = p.B[len(b):]
if len(p.B) == 0 && (len(b) == 0 || p.randN(2) == 0) {
return len(b), io.EOF
}
return len(b), p.mayError()
}
func (p *FaultyBuffer) Write(b []byte) (int, error) {
b2 := b[:p.mayTruncate(len(b))]
p.B = append(p.B, b2...)
if len(b2) < len(b) {
return len(b2), io.ErrShortWrite
}
return len(b2), p.mayError()
}
// mayTruncate may return a value between [0, n].
func (p *FaultyBuffer) mayTruncate(n int) int {
if p.MaxBytes > 0 {
if n > p.MaxBytes {
n = p.MaxBytes
}
return p.randN(n + 1)
}
return n
}
// mayError may return a non-nil error.
func (p *FaultyBuffer) mayError() error {
if p.MayError != nil && p.randN(2) == 0 {
return p.MayError
}
return nil
}
func (p *FaultyBuffer) randN(n int) int {
if p.Rand == nil {
p.Rand = rand.NewSource(0)
}
return int(p.Rand.Int63() % int64(n))
}

File diff suppressed because it is too large Load Diff

View File

@@ -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)
}
}

View File

@@ -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"
// }
}

View File

@@ -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
})
}

View File

@@ -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")
}
}
}

View File

@@ -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)
}
})
}
}

View File

@@ -1,200 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build goexperiment.jsonv2
package jsontext
import (
"io"
"strings"
"testing"
"encoding/json/internal/jsontest"
"encoding/json/internal/jsonwire"
)
type valueTestdataEntry struct {
name jsontest.CaseName
in string
wantValid bool
wantCompacted string
wantCompactErr error // implies wantCompacted is in
wantIndented string // wantCompacted if empty; uses "\t" for indent prefix and " " for indent
wantIndentErr error // implies wantCompacted is in
wantCanonicalized string // wantCompacted if empty
wantCanonicalizeErr error // implies wantCompacted is in
}
var valueTestdata = append(func() (out []valueTestdataEntry) {
// Initialize valueTestdata from coderTestdata.
for _, td := range coderTestdata {
// NOTE: The Compact method preserves the raw formatting of strings,
// while the Encoder (by default) does not.
if td.name.Name == "ComplicatedString" {
td.outCompacted = strings.TrimSpace(td.in)
}
out = append(out, valueTestdataEntry{
name: td.name,
in: td.in,
wantValid: true,
wantCompacted: td.outCompacted,
wantIndented: td.outIndented,
wantCanonicalized: td.outCanonicalized,
})
}
return out
}(), []valueTestdataEntry{{
name: jsontest.Name("RFC8785/Primitives"),
in: `{
"numbers": [333333333.33333329, 1E30, 4.50,
2e-3, 0.000000000000000000000000001, -0],
"string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
"literals": [null, true, false]
}`,
wantValid: true,
wantCompacted: `{"numbers":[333333333.33333329,1E30,4.50,2e-3,0.000000000000000000000000001,-0],"string":"\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/","literals":[null,true,false]}`,
wantIndented: `{
"numbers": [
333333333.33333329,
1E30,
4.50,
2e-3,
0.000000000000000000000000001,
-0
],
"string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
"literals": [
null,
true,
false
]
}`,
wantCanonicalized: `{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27,0],"string":"€$\u000f\nA'B\"\\\\\"/"}`,
}, {
name: jsontest.Name("RFC8785/ObjectOrdering"),
in: `{
"\u20ac": "Euro Sign",
"\r": "Carriage Return",
"\ufb33": "Hebrew Letter Dalet With Dagesh",
"1": "One",
"\ud83d\ude00": "Emoji: Grinning Face",
"\u0080": "Control",
"\u00f6": "Latin Small Letter O With Diaeresis"
}`,
wantValid: true,
wantCompacted: `{"\u20ac":"Euro Sign","\r":"Carriage Return","\ufb33":"Hebrew Letter Dalet With Dagesh","1":"One","\ud83d\ude00":"Emoji: Grinning Face","\u0080":"Control","\u00f6":"Latin Small Letter O With Diaeresis"}`,
wantIndented: `{
"\u20ac": "Euro Sign",
"\r": "Carriage Return",
"\ufb33": "Hebrew Letter Dalet With Dagesh",
"1": "One",
"\ud83d\ude00": "Emoji: Grinning Face",
"\u0080": "Control",
"\u00f6": "Latin Small Letter O With Diaeresis"
}`,
wantCanonicalized: `{"\r":"Carriage Return","1":"One","€":"Control","ö":"Latin Small Letter O With Diaeresis","€":"Euro Sign","😀":"Emoji: Grinning Face","דּ":"Hebrew Letter Dalet With Dagesh"}`,
}, {
name: jsontest.Name("LargeIntegers"),
in: ` [ -9223372036854775808 , 9223372036854775807 ] `,
wantValid: true,
wantCompacted: `[-9223372036854775808,9223372036854775807]`,
wantIndented: `[
-9223372036854775808,
9223372036854775807
]`,
wantCanonicalized: `[-9223372036854776000,9223372036854776000]`, // NOTE: Loss of precision due to numbers being treated as floats.
}, {
name: jsontest.Name("InvalidUTF8"),
in: ` "living` + "\xde\xad\xbe\xef" + `\ufffd<66>" `,
wantValid: false, // uses RFC 7493 as the definition; which validates UTF-8
wantCompacted: `"living` + "\xde\xad\xbe\xef" + `\ufffd<66>"`,
wantCanonicalizeErr: E(jsonwire.ErrInvalidUTF8).withPos(` "living`+"\xde\xad", ""),
}, {
name: jsontest.Name("InvalidUTF8/SurrogateHalf"),
in: `"\ud800"`,
wantValid: false, // uses RFC 7493 as the definition; which validates UTF-8
wantCompacted: `"\ud800"`,
wantCanonicalizeErr: newInvalidEscapeSequenceError(`\ud800"`).withPos(`"`, ""),
}, {
name: jsontest.Name("UppercaseEscaped"),
in: `"\u000B"`,
wantValid: true,
wantCompacted: `"\u000B"`,
wantCanonicalized: `"\u000b"`,
}, {
name: jsontest.Name("DuplicateNames"),
in: ` { "0" : 0 , "1" : 1 , "0" : 0 }`,
wantValid: false, // uses RFC 7493 as the definition; which does check for object uniqueness
wantCompacted: `{"0":0,"1":1,"0":0}`,
wantIndented: `{
"0": 0,
"1": 1,
"0": 0
}`,
wantCanonicalizeErr: E(ErrDuplicateName).withPos(` { "0" : 0 , "1" : 1 , `, "/0"),
}, {
name: jsontest.Name("Whitespace"),
in: " \n\r\t",
wantValid: false,
wantCompacted: " \n\r\t",
wantCompactErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""),
wantIndentErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""),
wantCanonicalizeErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""),
}}...)
func TestValueMethods(t *testing.T) {
for _, td := range valueTestdata {
t.Run(td.name.Name, func(t *testing.T) {
if td.wantIndented == "" {
td.wantIndented = td.wantCompacted
}
if td.wantCanonicalized == "" {
td.wantCanonicalized = td.wantCompacted
}
if td.wantCompactErr != nil {
td.wantCompacted = td.in
}
if td.wantIndentErr != nil {
td.wantIndented = td.in
}
if td.wantCanonicalizeErr != nil {
td.wantCanonicalized = td.in
}
v := Value(td.in)
gotValid := v.IsValid()
if gotValid != td.wantValid {
t.Errorf("%s: Value.IsValid = %v, want %v", td.name.Where, gotValid, td.wantValid)
}
gotCompacted := Value(td.in)
gotCompactErr := gotCompacted.Compact()
if string(gotCompacted) != td.wantCompacted {
t.Errorf("%s: Value.Compact = %s, want %s", td.name.Where, gotCompacted, td.wantCompacted)
}
if !equalError(gotCompactErr, td.wantCompactErr) {
t.Errorf("%s: Value.Compact error mismatch:\ngot %v\nwant %v", td.name.Where, gotCompactErr, td.wantCompactErr)
}
gotIndented := Value(td.in)
gotIndentErr := gotIndented.Indent(WithIndentPrefix("\t"), WithIndent(" "))
if string(gotIndented) != td.wantIndented {
t.Errorf("%s: Value.Indent = %s, want %s", td.name.Where, gotIndented, td.wantIndented)
}
if !equalError(gotIndentErr, td.wantIndentErr) {
t.Errorf("%s: Value.Indent error mismatch:\ngot %v\nwant %v", td.name.Where, gotIndentErr, td.wantIndentErr)
}
gotCanonicalized := Value(td.in)
gotCanonicalizeErr := gotCanonicalized.Canonicalize()
if string(gotCanonicalized) != td.wantCanonicalized {
t.Errorf("%s: Value.Canonicalize = %s, want %s", td.name.Where, gotCanonicalized, td.wantCanonicalized)
}
if !equalError(gotCanonicalizeErr, td.wantCanonicalizeErr) {
t.Errorf("%s: Value.Canonicalize error mismatch:\ngot %v\nwant %v", td.name.Where, gotCanonicalizeErr, td.wantCanonicalizeErr)
}
})
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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{"": "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","":"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",
>."": "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)
}
}

View File

@@ -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)
}
}
})
}
}

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"}
}

View File

@@ -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
}

View File

@@ -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
}
}
})
}

View File

@@ -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
}

View File

@@ -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{"": "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","":"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",
>."": "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)
}
}

View File

@@ -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)
}
}
})
}
}

View File

@@ -4,6 +4,8 @@
package tag
import (
"bytes"
"lol.mleku.dev/errorf"
"next.orly.dev/pkg/encoders/text"
"next.orly.dev/pkg/utils/bufpool"
@@ -25,11 +27,23 @@ func New(t ...[]byte) *T {
return &T{T: t, b: bufpool.Get()}
}
func NewWithCap(c int) *T {
return &T{T: make([][]byte, 0, c), b: bufpool.Get()}
}
func (t *T) Free() {
bufpool.Put(t.b)
t.T = nil
}
func (t *T) Len() int { return len(t.T) }
func (t *T) Less(i, j int) bool {
return bytes.Compare(t.T[i], t.T[j]) < 0
}
func (t *T) Swap(i, j int) { t.T[i], t.T[j] = t.T[j], t.T[i] }
// Marshal encodes a tag.T as standard minified JSON array of strings.
//
// Call bufpool.PutBytes(b) to return the buffer to the bufpool after use.

View File

@@ -17,14 +17,14 @@ func TestMarshalUnmarshal(t *testing.T) {
_, _ = frand.Read(b1)
tg.T = append(tg.T, b1)
}
tb := tg.Marshal()
tb := tg.Marshal(nil)
var tbc []byte
tbc = append(tbc, tb...)
tg2 := New()
if _, err := tg2.Unmarshal(tb); chk.E(err) {
t.Fatal(err)
}
tb2 := tg2.Marshal()
tb2 := tg2.Marshal(nil)
if !utils.FastEqual(tbc, tb2) {
t.Fatalf("failed to re-marshal back original")
}

View File

@@ -1,6 +1,8 @@
package tag
import (
"bytes"
"lol.mleku.dev/chk"
"next.orly.dev/pkg/utils/bufpool"
)
@@ -9,6 +11,26 @@ import (
// no uniqueness constraint (not a set).
type S []*T
func NewSWithCap(c int) (s *S) {
ss := make([]*T, 0, c)
return (*S)(&ss)
}
func (s *S) Len() int {
return len(*s)
}
func (s *S) Less(i, j int) bool {
// only the first element is compared, this is only used for normalizing
// filters and the individual tags must be separately sorted.
return bytes.Compare((*s)[i].T[0], (*s)[j].T[0]) < 0
}
func (s *S) Swap(i, j int) {
// TODO implement me
panic("implement me")
}
// MarshalJSON encodes a tags.T appended to a provided byte slice in JSON form.
//
// Call bufpool.PutBytes(b) to return the buffer to the bufpool after use.

View File

@@ -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)
}
},
)
}

View File

@@ -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
}

View File

@@ -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
}