implement filter codec
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
424
pkg/encoders/filter/filter.go
Normal file
424
pkg/encoders/filter/filter.go
Normal 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
|
||||
}
|
||||
102
pkg/encoders/filter/filter_test.go
Normal file
102
pkg/encoders/filter/filter_test.go
Normal 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 = ×tamp.T{int64(tn - frand.Intn(10000))}
|
||||
f.Until = timestamp.Now()
|
||||
if frand.Intn(10) > 5 {
|
||||
f.Limit = values.ToUintPointer(uint(frand.Intn(1000)))
|
||||
}
|
||||
f.Search = []byte("token search text")
|
||||
return
|
||||
}
|
||||
@@ -1,583 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Large data benchmark.
|
||||
// The JSON data is a summary of agl's changes in the
|
||||
// go, webkit, and chromium open source projects.
|
||||
// We benchmark converting between the JSON form
|
||||
// and in-memory data structures.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"internal/zstd"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type codeResponse struct {
|
||||
Tree *codeNode `json:"tree"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type codeNode struct {
|
||||
Name string `json:"name"`
|
||||
Kids []*codeNode `json:"kids"`
|
||||
CLWeight float64 `json:"cl_weight"`
|
||||
Touches int `json:"touches"`
|
||||
MinT int64 `json:"min_t"`
|
||||
MaxT int64 `json:"max_t"`
|
||||
MeanT int64 `json:"mean_t"`
|
||||
}
|
||||
|
||||
var codeJSON []byte
|
||||
var codeStruct codeResponse
|
||||
|
||||
func codeInit() {
|
||||
f, err := os.Open("internal/jsontest/testdata/golang_source.json.zst")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
gz := zstd.NewReader(f)
|
||||
data, err := io.ReadAll(gz)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
codeJSON = data
|
||||
|
||||
if err := Unmarshal(codeJSON, &codeStruct); err != nil {
|
||||
panic("unmarshal code.json: " + err.Error())
|
||||
}
|
||||
|
||||
if data, err = Marshal(&codeStruct); err != nil {
|
||||
panic("marshal code.json: " + err.Error())
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, codeJSON) {
|
||||
println("different lengths", len(data), len(codeJSON))
|
||||
for i := 0; i < len(data) && i < len(codeJSON); i++ {
|
||||
if data[i] != codeJSON[i] {
|
||||
println("re-marshal: changed at byte", i)
|
||||
println("orig: ", string(codeJSON[i-10:i+10]))
|
||||
println("new: ", string(data[i-10:i+10]))
|
||||
break
|
||||
}
|
||||
}
|
||||
panic("re-marshal code.json: different result")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCodeEncoder(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
enc := NewEncoder(io.Discard)
|
||||
for pb.Next() {
|
||||
if err := enc.Encode(&codeStruct); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeEncoderError(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
enc := NewEncoder(io.Discard)
|
||||
for pb.Next() {
|
||||
if err := enc.Encode(&codeStruct); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
if _, err := Marshal(dummy); err == nil {
|
||||
b.Fatal("Marshal error: got nil, want non-nil")
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeMarshal(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(&codeStruct); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeMarshalError(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(&codeStruct); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if _, err := Marshal(dummy); err == nil {
|
||||
b.Fatal("Marshal error: got nil, want non-nil")
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func benchMarshalBytes(n int) func(*testing.B) {
|
||||
sample := []byte("hello world")
|
||||
// Use a struct pointer, to avoid an allocation when passing it as an
|
||||
// interface parameter to Marshal.
|
||||
v := &struct {
|
||||
Bytes []byte
|
||||
}{
|
||||
bytes.Repeat(sample, (n/len(sample))+1)[:n],
|
||||
}
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := Marshal(v); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchMarshalBytesError(n int) func(*testing.B) {
|
||||
sample := []byte("hello world")
|
||||
// Use a struct pointer, to avoid an allocation when passing it as an
|
||||
// interface parameter to Marshal.
|
||||
v := &struct {
|
||||
Bytes []byte
|
||||
}{
|
||||
bytes.Repeat(sample, (n/len(sample))+1)[:n],
|
||||
}
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := Marshal(v); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if _, err := Marshal(dummy); err == nil {
|
||||
b.Fatal("Marshal error: got nil, want non-nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalBytes(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
// 32 fits within encodeState.scratch.
|
||||
b.Run("32", benchMarshalBytes(32))
|
||||
// 256 doesn't fit in encodeState.scratch, but is small enough to
|
||||
// allocate and avoid the slower base64.NewEncoder.
|
||||
b.Run("256", benchMarshalBytes(256))
|
||||
// 4096 is large enough that we want to avoid allocating for it.
|
||||
b.Run("4096", benchMarshalBytes(4096))
|
||||
}
|
||||
|
||||
func BenchmarkMarshalBytesError(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
// 32 fits within encodeState.scratch.
|
||||
b.Run("32", benchMarshalBytesError(32))
|
||||
// 256 doesn't fit in encodeState.scratch, but is small enough to
|
||||
// allocate and avoid the slower base64.NewEncoder.
|
||||
b.Run("256", benchMarshalBytesError(256))
|
||||
// 4096 is large enough that we want to avoid allocating for it.
|
||||
b.Run("4096", benchMarshalBytesError(4096))
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMap(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
m := map[string]int{
|
||||
"key3": 3,
|
||||
"key2": 2,
|
||||
"key1": 1,
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(m); err != nil {
|
||||
b.Fatal("Marshal:", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCodeDecoder(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var buf bytes.Buffer
|
||||
dec := NewDecoder(&buf)
|
||||
var r codeResponse
|
||||
for pb.Next() {
|
||||
buf.Write(codeJSON)
|
||||
// hide EOF
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteByte('\n')
|
||||
if err := dec.Decode(&r); err != nil {
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkUnicodeDecoder(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := []byte(`"\uD83D\uDE01"`)
|
||||
b.SetBytes(int64(len(j)))
|
||||
r := bytes.NewReader(j)
|
||||
dec := NewDecoder(r)
|
||||
var out string
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := dec.Decode(&out); err != nil {
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
r.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecoderStream(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.StopTimer()
|
||||
var buf bytes.Buffer
|
||||
dec := NewDecoder(&buf)
|
||||
buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n")
|
||||
var x any
|
||||
if err := dec.Decode(&x); err != nil {
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
ones := strings.Repeat(" 1\n", 300000) + "\n\n\n"
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%300000 == 0 {
|
||||
buf.WriteString(ones)
|
||||
}
|
||||
x = nil
|
||||
switch err := dec.Decode(&x); {
|
||||
case err != nil:
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
case x != 1.0:
|
||||
b.Fatalf("Decode: got %v want 1.0", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCodeUnmarshal(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var r codeResponse
|
||||
if err := Unmarshal(codeJSON, &r); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeUnmarshalReuse(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var r codeResponse
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(codeJSON, &r); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalString(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`"hello, world"`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var s string
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &s); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalFloat64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`3.14`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var f float64
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &f); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalInt64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`3`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var x int64
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &x); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalMap(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`{"key1":"value1","key2":"value2","key3":"value3"}`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
x := make(map[string]string, 3)
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &x); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIssue10335(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := []byte(`{"a":{ }}`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var s struct{}
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(j, &s); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIssue34127(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := struct {
|
||||
Bar string `json:"bar,string"`
|
||||
}{
|
||||
Bar: `foobar`,
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(&j); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmapped(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var s struct{}
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(j, &s); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTypeFieldsCache(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
var maxTypes int = 1e6
|
||||
if testenv.Builder() != "" {
|
||||
maxTypes = 1e3 // restrict cache sizes on builders
|
||||
}
|
||||
|
||||
// Dynamically generate many new types.
|
||||
types := make([]reflect.Type, maxTypes)
|
||||
fs := []reflect.StructField{{
|
||||
Type: reflect.TypeFor[string](),
|
||||
Index: []int{0},
|
||||
}}
|
||||
for i := range types {
|
||||
fs[0].Name = fmt.Sprintf("TypeFieldsCache%d", i)
|
||||
types[i] = reflect.StructOf(fs)
|
||||
}
|
||||
|
||||
// clearClear clears the cache. Other JSON operations, must not be running.
|
||||
clearCache := func() {
|
||||
fieldCache = sync.Map{}
|
||||
}
|
||||
|
||||
// MissTypes tests the performance of repeated cache misses.
|
||||
// This measures the time to rebuild a cache of size nt.
|
||||
for nt := 1; nt <= maxTypes; nt *= 10 {
|
||||
ts := types[:nt]
|
||||
b.Run(fmt.Sprintf("MissTypes%d", nt), func(b *testing.B) {
|
||||
nc := runtime.GOMAXPROCS(0)
|
||||
for i := 0; i < b.N; i++ {
|
||||
clearCache()
|
||||
var wg sync.WaitGroup
|
||||
for j := 0; j < nc; j++ {
|
||||
wg.Add(1)
|
||||
go func(j int) {
|
||||
for _, t := range ts[(j*len(ts))/nc : ((j+1)*len(ts))/nc] {
|
||||
cachedTypeFields(t)
|
||||
}
|
||||
wg.Done()
|
||||
}(j)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// HitTypes tests the performance of repeated cache hits.
|
||||
// This measures the average time of each cache lookup.
|
||||
for nt := 1; nt <= maxTypes; nt *= 10 {
|
||||
// Pre-warm a cache of size nt.
|
||||
clearCache()
|
||||
for _, t := range types[:nt] {
|
||||
cachedTypeFields(t)
|
||||
}
|
||||
b.Run(fmt.Sprintf("HitTypes%d", nt), func(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
cachedTypeFields(types[0])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeMarshaler(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
m := struct {
|
||||
A int
|
||||
B RawMessage
|
||||
}{}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
enc := NewEncoder(io.Discard)
|
||||
|
||||
for pb.Next() {
|
||||
if err := enc.Encode(&m); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkEncoderEncode(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
type T struct {
|
||||
X, Y string
|
||||
}
|
||||
v := &T{"foo", "bar"}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := NewEncoder(io.Discard).Encode(v); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNumberIsValid(b *testing.B) {
|
||||
s := "-61657.61667E+61673"
|
||||
for i := 0; i < b.N; i++ {
|
||||
isValidNumber(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNumberIsValidRegexp(b *testing.B) {
|
||||
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
|
||||
s := "-61657.61667E+61673"
|
||||
for i := 0; i < b.N; i++ {
|
||||
jsonNumberRegexp.MatchString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalNumber(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`"-61657.61667E+61673"`)
|
||||
var number Number
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := Unmarshal(data, &number); err != nil {
|
||||
b.Fatal("Unmarshal:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,75 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Animal int
|
||||
|
||||
const (
|
||||
Unknown Animal = iota
|
||||
Gopher
|
||||
Zebra
|
||||
)
|
||||
|
||||
func (a *Animal) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
switch strings.ToLower(s) {
|
||||
default:
|
||||
*a = Unknown
|
||||
case "gopher":
|
||||
*a = Gopher
|
||||
case "zebra":
|
||||
*a = Zebra
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a Animal) MarshalJSON() ([]byte, error) {
|
||||
var s string
|
||||
switch a {
|
||||
default:
|
||||
s = "unknown"
|
||||
case Gopher:
|
||||
s = "gopher"
|
||||
case Zebra:
|
||||
s = "zebra"
|
||||
}
|
||||
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func Example_customMarshalJSON() {
|
||||
blob := `["gopher","armadillo","zebra","unknown","gopher","bee","gopher","zebra"]`
|
||||
var zoo []Animal
|
||||
if err := json.Unmarshal([]byte(blob), &zoo); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
census := make(map[Animal]int)
|
||||
for _, animal := range zoo {
|
||||
census[animal] += 1
|
||||
}
|
||||
|
||||
fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n",
|
||||
census[Gopher], census[Zebra], census[Unknown])
|
||||
|
||||
// Output:
|
||||
// Zoo Census:
|
||||
// * Gophers: 3
|
||||
// * Zebras: 2
|
||||
// * Unknown: 3
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ExampleMarshal() {
|
||||
type ColorGroup struct {
|
||||
ID int
|
||||
Name string
|
||||
Colors []string
|
||||
}
|
||||
group := ColorGroup{
|
||||
ID: 1,
|
||||
Name: "Reds",
|
||||
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
|
||||
}
|
||||
b, err := json.Marshal(group)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
// Output:
|
||||
// {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
|
||||
}
|
||||
|
||||
func ExampleUnmarshal() {
|
||||
var jsonBlob = []byte(`[
|
||||
{"Name": "Platypus", "Order": "Monotremata"},
|
||||
{"Name": "Quoll", "Order": "Dasyuromorphia"}
|
||||
]`)
|
||||
type Animal struct {
|
||||
Name string
|
||||
Order string
|
||||
}
|
||||
var animals []Animal
|
||||
err := json.Unmarshal(jsonBlob, &animals)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
fmt.Printf("%+v", animals)
|
||||
// Output:
|
||||
// [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
|
||||
}
|
||||
|
||||
// This example uses a Decoder to decode a stream of distinct JSON values.
|
||||
func ExampleDecoder() {
|
||||
const jsonStream = `
|
||||
{"Name": "Ed", "Text": "Knock knock."}
|
||||
{"Name": "Sam", "Text": "Who's there?"}
|
||||
{"Name": "Ed", "Text": "Go fmt."}
|
||||
{"Name": "Sam", "Text": "Go fmt who?"}
|
||||
{"Name": "Ed", "Text": "Go fmt yourself!"}
|
||||
`
|
||||
type Message struct {
|
||||
Name, Text string
|
||||
}
|
||||
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||
for {
|
||||
var m Message
|
||||
if err := dec.Decode(&m); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s: %s\n", m.Name, m.Text)
|
||||
}
|
||||
// Output:
|
||||
// Ed: Knock knock.
|
||||
// Sam: Who's there?
|
||||
// Ed: Go fmt.
|
||||
// Sam: Go fmt who?
|
||||
// Ed: Go fmt yourself!
|
||||
}
|
||||
|
||||
// This example uses a Decoder to decode a stream of distinct JSON values.
|
||||
func ExampleDecoder_Token() {
|
||||
const jsonStream = `
|
||||
{"Message": "Hello", "Array": [1, 2, 3], "Null": null, "Number": 1.234}
|
||||
`
|
||||
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||
for {
|
||||
t, err := dec.Token()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%T: %v", t, t)
|
||||
if dec.More() {
|
||||
fmt.Printf(" (more)")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
// Output:
|
||||
// json.Delim: { (more)
|
||||
// string: Message (more)
|
||||
// string: Hello (more)
|
||||
// string: Array (more)
|
||||
// json.Delim: [ (more)
|
||||
// float64: 1 (more)
|
||||
// float64: 2 (more)
|
||||
// float64: 3
|
||||
// json.Delim: ] (more)
|
||||
// string: Null (more)
|
||||
// <nil>: <nil> (more)
|
||||
// string: Number (more)
|
||||
// float64: 1.234
|
||||
// json.Delim: }
|
||||
}
|
||||
|
||||
// This example uses a Decoder to decode a streaming array of JSON objects.
|
||||
func ExampleDecoder_Decode_stream() {
|
||||
const jsonStream = `
|
||||
[
|
||||
{"Name": "Ed", "Text": "Knock knock."},
|
||||
{"Name": "Sam", "Text": "Who's there?"},
|
||||
{"Name": "Ed", "Text": "Go fmt."},
|
||||
{"Name": "Sam", "Text": "Go fmt who?"},
|
||||
{"Name": "Ed", "Text": "Go fmt yourself!"}
|
||||
]
|
||||
`
|
||||
type Message struct {
|
||||
Name, Text string
|
||||
}
|
||||
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||
|
||||
// read open bracket
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%T: %v\n", t, t)
|
||||
|
||||
// while the array contains values
|
||||
for dec.More() {
|
||||
var m Message
|
||||
// decode an array value (Message)
|
||||
err := dec.Decode(&m)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v: %v\n", m.Name, m.Text)
|
||||
}
|
||||
|
||||
// read closing bracket
|
||||
t, err = dec.Token()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%T: %v\n", t, t)
|
||||
|
||||
// Output:
|
||||
// json.Delim: [
|
||||
// Ed: Knock knock.
|
||||
// Sam: Who's there?
|
||||
// Ed: Go fmt.
|
||||
// Sam: Go fmt who?
|
||||
// Ed: Go fmt yourself!
|
||||
// json.Delim: ]
|
||||
}
|
||||
|
||||
// This example uses RawMessage to delay parsing part of a JSON message.
|
||||
func ExampleRawMessage_unmarshal() {
|
||||
type Color struct {
|
||||
Space string
|
||||
Point json.RawMessage // delay parsing until we know the color space
|
||||
}
|
||||
type RGB struct {
|
||||
R uint8
|
||||
G uint8
|
||||
B uint8
|
||||
}
|
||||
type YCbCr struct {
|
||||
Y uint8
|
||||
Cb int8
|
||||
Cr int8
|
||||
}
|
||||
|
||||
var j = []byte(`[
|
||||
{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
|
||||
{"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}}
|
||||
]`)
|
||||
var colors []Color
|
||||
err := json.Unmarshal(j, &colors)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
|
||||
for _, c := range colors {
|
||||
var dst any
|
||||
switch c.Space {
|
||||
case "RGB":
|
||||
dst = new(RGB)
|
||||
case "YCbCr":
|
||||
dst = new(YCbCr)
|
||||
}
|
||||
err := json.Unmarshal(c.Point, dst)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
fmt.Println(c.Space, dst)
|
||||
}
|
||||
// Output:
|
||||
// YCbCr &{255 0 -10}
|
||||
// RGB &{98 218 255}
|
||||
}
|
||||
|
||||
// This example uses RawMessage to use a precomputed JSON during marshal.
|
||||
func ExampleRawMessage_marshal() {
|
||||
h := json.RawMessage(`{"precomputed": true}`)
|
||||
|
||||
c := struct {
|
||||
Header *json.RawMessage `json:"header"`
|
||||
Body string `json:"body"`
|
||||
}{Header: &h, Body: "Hello Gophers!"}
|
||||
|
||||
b, err := json.MarshalIndent(&c, "", "\t")
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
|
||||
// Output:
|
||||
// {
|
||||
// "header": {
|
||||
// "precomputed": true
|
||||
// },
|
||||
// "body": "Hello Gophers!"
|
||||
// }
|
||||
}
|
||||
|
||||
func ExampleIndent() {
|
||||
type Road struct {
|
||||
Name string
|
||||
Number int
|
||||
}
|
||||
roads := []Road{
|
||||
{"Diamond Fork", 29},
|
||||
{"Sheep Creek", 51},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(roads)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
json.Indent(&out, b, "=", "\t")
|
||||
out.WriteTo(os.Stdout)
|
||||
// Output:
|
||||
// [
|
||||
// = {
|
||||
// = "Name": "Diamond Fork",
|
||||
// = "Number": 29
|
||||
// = },
|
||||
// = {
|
||||
// = "Name": "Sheep Creek",
|
||||
// = "Number": 51
|
||||
// = }
|
||||
// =]
|
||||
}
|
||||
|
||||
func ExampleMarshalIndent() {
|
||||
data := map[string]int{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(data, "<prefix>", "<indent>")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
// Output:
|
||||
// {
|
||||
// <prefix><indent>"a": 1,
|
||||
// <prefix><indent>"b": 2
|
||||
// <prefix>}
|
||||
}
|
||||
|
||||
func ExampleValid() {
|
||||
goodJSON := `{"example": 1}`
|
||||
badJSON := `{"example":2:]}}`
|
||||
|
||||
fmt.Println(json.Valid([]byte(goodJSON)), json.Valid([]byte(badJSON)))
|
||||
// Output:
|
||||
// true false
|
||||
}
|
||||
|
||||
func ExampleHTMLEscape() {
|
||||
var out bytes.Buffer
|
||||
json.HTMLEscape(&out, []byte(`{"Name":"<b>HTML content</b>"}`))
|
||||
out.WriteTo(os.Stdout)
|
||||
// Output:
|
||||
//{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Size int
|
||||
|
||||
const (
|
||||
Unrecognized Size = iota
|
||||
Small
|
||||
Large
|
||||
)
|
||||
|
||||
func (s *Size) UnmarshalText(text []byte) error {
|
||||
switch strings.ToLower(string(text)) {
|
||||
default:
|
||||
*s = Unrecognized
|
||||
case "small":
|
||||
*s = Small
|
||||
case "large":
|
||||
*s = Large
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Size) MarshalText() ([]byte, error) {
|
||||
var name string
|
||||
switch s {
|
||||
default:
|
||||
name = "unrecognized"
|
||||
case Small:
|
||||
name = "small"
|
||||
case Large:
|
||||
name = "large"
|
||||
}
|
||||
return []byte(name), nil
|
||||
}
|
||||
|
||||
func Example_textMarshalJSON() {
|
||||
blob := `["small","regular","large","unrecognized","small","normal","small","large"]`
|
||||
var inventory []Size
|
||||
if err := json.Unmarshal([]byte(blob), &inventory); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
counts := make(map[Size]int)
|
||||
for _, size := range inventory {
|
||||
counts[size] += 1
|
||||
}
|
||||
|
||||
fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n",
|
||||
counts[Small], counts[Large], counts[Unrecognized])
|
||||
|
||||
// Output:
|
||||
// Inventory Counts:
|
||||
// * Small: 3
|
||||
// * Large: 2
|
||||
// * Unrecognized: 3
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzEqualFold(f *testing.F) {
|
||||
for _, ss := range [][2]string{
|
||||
{"", ""},
|
||||
{"123abc", "123ABC"},
|
||||
{"αβδ", "ΑΒΔ"},
|
||||
{"abc", "xyz"},
|
||||
{"abc", "XYZ"},
|
||||
{"1", "2"},
|
||||
{"hello, world!", "hello, world!"},
|
||||
{"hello, world!", "Hello, World!"},
|
||||
{"hello, world!", "HELLO, WORLD!"},
|
||||
{"hello, world!", "jello, world!"},
|
||||
{"γειά, κόσμε!", "γειά, κόσμε!"},
|
||||
{"γειά, κόσμε!", "Γειά, Κόσμε!"},
|
||||
{"γειά, κόσμε!", "ΓΕΙΆ, ΚΌΣΜΕ!"},
|
||||
{"γειά, κόσμε!", "ΛΕΙΆ, ΚΌΣΜΕ!"},
|
||||
{"AESKey", "aesKey"},
|
||||
{"AESKEY", "aes_key"},
|
||||
{"aes_key", "AES_KEY"},
|
||||
{"AES_KEY", "aes-key"},
|
||||
{"aes-key", "AES-KEY"},
|
||||
{"AES-KEY", "aesKey"},
|
||||
{"aesKey", "AesKey"},
|
||||
{"AesKey", "AESKey"},
|
||||
{"AESKey", "aeskey"},
|
||||
{"DESKey", "aeskey"},
|
||||
{"AES Key", "aeskey"},
|
||||
} {
|
||||
f.Add([]byte(ss[0]), []byte(ss[1]))
|
||||
}
|
||||
equalFold := func(x, y []byte) bool { return string(foldName(x)) == string(foldName(y)) }
|
||||
f.Fuzz(func(t *testing.T, x, y []byte) {
|
||||
got := equalFold(x, y)
|
||||
want := bytes.EqualFold(x, y)
|
||||
if got != want {
|
||||
t.Errorf("equalFold(%q, %q) = %v, want %v", x, y, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzUnmarshalJSON(f *testing.F) {
|
||||
f.Add([]byte(`{
|
||||
"object": {
|
||||
"slice": [
|
||||
1,
|
||||
2.0,
|
||||
"3",
|
||||
[4],
|
||||
{5: {}}
|
||||
]
|
||||
},
|
||||
"slice": [[]],
|
||||
"string": ":)",
|
||||
"int": 1e5,
|
||||
"float": 3e-9"
|
||||
}`))
|
||||
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
for _, typ := range []func() any{
|
||||
func() any { return new(any) },
|
||||
func() any { return new(map[string]any) },
|
||||
func() any { return new([]any) },
|
||||
} {
|
||||
i := typ()
|
||||
if err := Unmarshal(b, i); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encoded, err := Marshal(i)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %s", err)
|
||||
}
|
||||
|
||||
if err := Unmarshal(encoded, i); err != nil {
|
||||
t.Fatalf("failed to roundtrip: %s", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDecoderToken(f *testing.F) {
|
||||
f.Add([]byte(`{
|
||||
"object": {
|
||||
"slice": [
|
||||
1,
|
||||
2.0,
|
||||
"3",
|
||||
[4],
|
||||
{5: {}}
|
||||
]
|
||||
},
|
||||
"slice": [[]],
|
||||
"string": ":)",
|
||||
"int": 1e5,
|
||||
"float": 3e-9"
|
||||
}`))
|
||||
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
r := bytes.NewReader(b)
|
||||
d := NewDecoder(r)
|
||||
for {
|
||||
_, err := d.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsonflags
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
type Check struct{ want Flags }
|
||||
type Join struct{ in Flags }
|
||||
type Set struct{ in Bools }
|
||||
type Clear struct{ in Bools }
|
||||
type Get struct {
|
||||
in Bools
|
||||
want bool
|
||||
wantOk bool
|
||||
}
|
||||
|
||||
calls := []any{
|
||||
Get{in: AllowDuplicateNames, want: false, wantOk: false},
|
||||
Set{in: AllowDuplicateNames | 0},
|
||||
Get{in: AllowDuplicateNames, want: false, wantOk: true},
|
||||
Set{in: AllowDuplicateNames | 1},
|
||||
Get{in: AllowDuplicateNames, want: true, wantOk: true},
|
||||
Check{want: Flags{Presence: uint64(AllowDuplicateNames), Values: uint64(AllowDuplicateNames)}},
|
||||
Get{in: AllowInvalidUTF8, want: false, wantOk: false},
|
||||
Set{in: AllowInvalidUTF8 | 1},
|
||||
Get{in: AllowInvalidUTF8, want: true, wantOk: true},
|
||||
Set{in: AllowInvalidUTF8 | 0},
|
||||
Get{in: AllowInvalidUTF8, want: false, wantOk: true},
|
||||
Get{in: AllowDuplicateNames, want: true, wantOk: true},
|
||||
Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}},
|
||||
Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 0},
|
||||
Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(0)}},
|
||||
Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 0},
|
||||
Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(0)}},
|
||||
Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 1},
|
||||
Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames | AllowInvalidUTF8)}},
|
||||
Join{in: Flags{Presence: 0, Values: 0}},
|
||||
Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames | AllowInvalidUTF8)}},
|
||||
Join{in: Flags{Presence: uint64(Multiline | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}},
|
||||
Check{want: Flags{Presence: uint64(Multiline | AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}},
|
||||
Clear{in: AllowDuplicateNames | AllowInvalidUTF8},
|
||||
Check{want: Flags{Presence: uint64(Multiline), Values: uint64(0)}},
|
||||
Set{in: AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | 1},
|
||||
Set{in: Multiline | StringifyNumbers | 0},
|
||||
Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | Multiline | StringifyNumbers), Values: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics)}},
|
||||
Clear{in: ^AllCoderFlags},
|
||||
Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Multiline), Values: uint64(AllowInvalidUTF8)}},
|
||||
}
|
||||
var fs Flags
|
||||
for i, call := range calls {
|
||||
switch call := call.(type) {
|
||||
case Join:
|
||||
fs.Join(call.in)
|
||||
case Set:
|
||||
fs.Set(call.in)
|
||||
case Clear:
|
||||
fs.Clear(call.in)
|
||||
case Get:
|
||||
got := fs.Get(call.in)
|
||||
gotOk := fs.Has(call.in)
|
||||
if got != call.want || gotOk != call.wantOk {
|
||||
t.Fatalf("%d: GetOk = (%v, %v), want (%v, %v)", i, got, gotOk, call.want, call.wantOk)
|
||||
}
|
||||
case Check:
|
||||
if fs != call.want {
|
||||
t.Fatalf("%d: got %x, want %x", i, fs, call.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsonopts_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"encoding/json/internal/jsonflags"
|
||||
. "encoding/json/internal/jsonopts"
|
||||
"encoding/json/jsontext"
|
||||
"encoding/json/v2"
|
||||
)
|
||||
|
||||
func makeFlags(f ...jsonflags.Bools) (fs jsonflags.Flags) {
|
||||
for _, f := range f {
|
||||
fs.Set(f)
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
tests := []struct {
|
||||
in Options
|
||||
excludeCoders bool
|
||||
want *Struct
|
||||
}{{
|
||||
in: jsonflags.AllowInvalidUTF8 | 1,
|
||||
want: &Struct{Flags: makeFlags(jsonflags.AllowInvalidUTF8 | 1)},
|
||||
}, {
|
||||
in: jsonflags.Multiline | 0,
|
||||
want: &Struct{
|
||||
Flags: makeFlags(jsonflags.AllowInvalidUTF8|1, jsonflags.Multiline|0)},
|
||||
}, {
|
||||
in: Indent("\t"), // implicitly sets Multiline=true
|
||||
want: &Struct{
|
||||
Flags: makeFlags(jsonflags.AllowInvalidUTF8 | jsonflags.Multiline | jsonflags.Indent | 1),
|
||||
CoderValues: CoderValues{Indent: "\t"},
|
||||
},
|
||||
}, {
|
||||
in: &Struct{
|
||||
Flags: makeFlags(jsonflags.Multiline|jsonflags.EscapeForJS|0, jsonflags.AllowInvalidUTF8|1),
|
||||
},
|
||||
want: &Struct{
|
||||
Flags: makeFlags(jsonflags.AllowInvalidUTF8|jsonflags.Indent|1, jsonflags.Multiline|jsonflags.EscapeForJS|0),
|
||||
CoderValues: CoderValues{Indent: "\t"},
|
||||
},
|
||||
}, {
|
||||
in: &DefaultOptionsV1,
|
||||
want: func() *Struct {
|
||||
v1 := DefaultOptionsV1
|
||||
v1.Flags.Set(jsonflags.Indent | 1)
|
||||
v1.Flags.Set(jsonflags.Multiline | 0)
|
||||
v1.Indent = "\t"
|
||||
return &v1
|
||||
}(), // v1 fully replaces before (except for whitespace related flags)
|
||||
}, {
|
||||
in: &DefaultOptionsV2,
|
||||
want: func() *Struct {
|
||||
v2 := DefaultOptionsV2
|
||||
v2.Flags.Set(jsonflags.Indent | 1)
|
||||
v2.Flags.Set(jsonflags.Multiline | 0)
|
||||
v2.Indent = "\t"
|
||||
return &v2
|
||||
}(), // v2 fully replaces before (except for whitespace related flags)
|
||||
}, {
|
||||
in: jsonflags.Deterministic | jsonflags.AllowInvalidUTF8 | 1, excludeCoders: true,
|
||||
want: func() *Struct {
|
||||
v2 := DefaultOptionsV2
|
||||
v2.Flags.Set(jsonflags.Deterministic | 1)
|
||||
v2.Flags.Set(jsonflags.Indent | 1)
|
||||
v2.Flags.Set(jsonflags.Multiline | 0)
|
||||
v2.Indent = "\t"
|
||||
return &v2
|
||||
}(),
|
||||
}, {
|
||||
in: jsontext.WithIndentPrefix(" "), excludeCoders: true,
|
||||
want: func() *Struct {
|
||||
v2 := DefaultOptionsV2
|
||||
v2.Flags.Set(jsonflags.Deterministic | 1)
|
||||
v2.Flags.Set(jsonflags.Indent | 1)
|
||||
v2.Flags.Set(jsonflags.Multiline | 0)
|
||||
v2.Indent = "\t"
|
||||
return &v2
|
||||
}(),
|
||||
}, {
|
||||
in: jsontext.WithIndentPrefix(" "), excludeCoders: false,
|
||||
want: func() *Struct {
|
||||
v2 := DefaultOptionsV2
|
||||
v2.Flags.Set(jsonflags.Deterministic | 1)
|
||||
v2.Flags.Set(jsonflags.Indent | 1)
|
||||
v2.Flags.Set(jsonflags.IndentPrefix | 1)
|
||||
v2.Flags.Set(jsonflags.Multiline | 1)
|
||||
v2.Indent = "\t"
|
||||
v2.IndentPrefix = " "
|
||||
return &v2
|
||||
}(),
|
||||
}, {
|
||||
in: &Struct{
|
||||
Flags: jsonflags.Flags{
|
||||
Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix),
|
||||
Values: uint64(jsonflags.Indent | jsonflags.IndentPrefix),
|
||||
},
|
||||
CoderValues: CoderValues{Indent: " ", IndentPrefix: " "},
|
||||
},
|
||||
excludeCoders: true,
|
||||
want: func() *Struct {
|
||||
v2 := DefaultOptionsV2
|
||||
v2.Flags.Set(jsonflags.Indent | 1)
|
||||
v2.Flags.Set(jsonflags.IndentPrefix | 1)
|
||||
v2.Flags.Set(jsonflags.Multiline | 1)
|
||||
v2.Indent = "\t"
|
||||
v2.IndentPrefix = " "
|
||||
return &v2
|
||||
}(),
|
||||
}, {
|
||||
in: &Struct{
|
||||
Flags: jsonflags.Flags{
|
||||
Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix),
|
||||
Values: uint64(jsonflags.Indent | jsonflags.IndentPrefix),
|
||||
},
|
||||
CoderValues: CoderValues{Indent: " ", IndentPrefix: " "},
|
||||
},
|
||||
excludeCoders: false,
|
||||
want: func() *Struct {
|
||||
v2 := DefaultOptionsV2
|
||||
v2.Flags.Set(jsonflags.Indent | 1)
|
||||
v2.Flags.Set(jsonflags.IndentPrefix | 1)
|
||||
v2.Flags.Set(jsonflags.Multiline | 1)
|
||||
v2.Indent = " "
|
||||
v2.IndentPrefix = " "
|
||||
return &v2
|
||||
}(),
|
||||
}}
|
||||
got := new(Struct)
|
||||
for i, tt := range tests {
|
||||
if tt.excludeCoders {
|
||||
got.JoinWithoutCoderOptions(tt.in)
|
||||
} else {
|
||||
got.Join(tt.in)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("%d: Join:\n\tgot: %+v\n\twant: %+v", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
opts := &Struct{
|
||||
Flags: makeFlags(jsonflags.Indent|jsonflags.Deterministic|jsonflags.Marshalers|1, jsonflags.Multiline|0),
|
||||
CoderValues: CoderValues{Indent: "\t"},
|
||||
ArshalValues: ArshalValues{Marshalers: new(json.Marshalers)},
|
||||
}
|
||||
if v, ok := json.GetOption(nil, jsontext.AllowDuplicateNames); v || ok {
|
||||
t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(jsonflags.AllowInvalidUTF8|0, jsontext.AllowDuplicateNames); v || ok {
|
||||
t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|0, jsontext.AllowDuplicateNames); v || !ok {
|
||||
t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, true)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.AllowDuplicateNames); !v || !ok {
|
||||
t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (true, true)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(Indent(""), jsontext.AllowDuplicateNames); v || ok {
|
||||
t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(Indent(" "), jsontext.WithIndent); v != " " || !ok {
|
||||
t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want (" ", true)`, v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.WithIndent); v != "" || ok {
|
||||
t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("", false)`, v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, jsontext.AllowDuplicateNames); v || ok {
|
||||
t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, json.Deterministic); !v || !ok {
|
||||
t.Errorf("GetOption(..., Deterministic) = (%v, %v), want (true, true)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, jsontext.Multiline); v || !ok {
|
||||
t.Errorf("GetOption(..., Multiline) = (%v, %v), want (false, true)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, jsontext.AllowInvalidUTF8); v || ok {
|
||||
t.Errorf("GetOption(..., AllowInvalidUTF8) = (%v, %v), want (false, false)", v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, jsontext.WithIndent); v != "\t" || !ok {
|
||||
t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("\t", true)`, v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, jsontext.WithIndentPrefix); v != "" || ok {
|
||||
t.Errorf(`GetOption(..., WithIndentPrefix) = (%q, %v), want ("", false)`, v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, json.WithMarshalers); v == nil || !ok {
|
||||
t.Errorf(`GetOption(..., WithMarshalers) = (%v, %v), want (non-nil, true)`, v, ok)
|
||||
}
|
||||
if v, ok := json.GetOption(opts, json.WithUnmarshalers); v != nil || ok {
|
||||
t.Errorf(`GetOption(..., WithUnmarshalers) = (%v, %v), want (nil, false)`, v, ok)
|
||||
}
|
||||
}
|
||||
|
||||
var sink struct {
|
||||
Bool bool
|
||||
String string
|
||||
Marshalers *json.Marshalers
|
||||
}
|
||||
|
||||
func BenchmarkGetBool(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
opts := json.DefaultOptionsV2()
|
||||
for range b.N {
|
||||
sink.Bool, sink.Bool = json.GetOption(opts, jsontext.AllowDuplicateNames)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetIndent(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
opts := json.DefaultOptionsV2()
|
||||
for range b.N {
|
||||
sink.String, sink.Bool = json.GetOption(opts, jsontext.WithIndent)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetMarshalers(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
opts := json.JoinOptions(json.DefaultOptionsV2(), json.WithMarshalers(nil))
|
||||
for range b.N {
|
||||
sink.Marshalers, sink.Bool = json.GetOption(opts, json.WithMarshalers)
|
||||
}
|
||||
}
|
||||
@@ -1,443 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsonwire
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConsumeWhitespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want int
|
||||
}{
|
||||
{"", 0},
|
||||
{"a", 0},
|
||||
{" a", 1},
|
||||
{" a ", 1},
|
||||
{" \n\r\ta", 4},
|
||||
{" \n\r\t \n\r\t \n\r\t \n\r\t", 16},
|
||||
{"\u00a0", 0}, // non-breaking space is not JSON whitespace
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
if got := ConsumeWhitespace([]byte(tt.in)); got != tt.want {
|
||||
t.Errorf("ConsumeWhitespace(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumeLiteral(t *testing.T) {
|
||||
tests := []struct {
|
||||
literal string
|
||||
in string
|
||||
want int
|
||||
wantErr error
|
||||
}{
|
||||
{"null", "", 0, io.ErrUnexpectedEOF},
|
||||
{"null", "n", 1, io.ErrUnexpectedEOF},
|
||||
{"null", "nu", 2, io.ErrUnexpectedEOF},
|
||||
{"null", "nul", 3, io.ErrUnexpectedEOF},
|
||||
{"null", "null", 4, nil},
|
||||
{"null", "nullx", 4, nil},
|
||||
{"null", "x", 0, NewInvalidCharacterError("x", "in literal null (expecting 'n')")},
|
||||
{"null", "nuxx", 2, NewInvalidCharacterError("x", "in literal null (expecting 'l')")},
|
||||
|
||||
{"false", "", 0, io.ErrUnexpectedEOF},
|
||||
{"false", "f", 1, io.ErrUnexpectedEOF},
|
||||
{"false", "fa", 2, io.ErrUnexpectedEOF},
|
||||
{"false", "fal", 3, io.ErrUnexpectedEOF},
|
||||
{"false", "fals", 4, io.ErrUnexpectedEOF},
|
||||
{"false", "false", 5, nil},
|
||||
{"false", "falsex", 5, nil},
|
||||
{"false", "x", 0, NewInvalidCharacterError("x", "in literal false (expecting 'f')")},
|
||||
{"false", "falsx", 4, NewInvalidCharacterError("x", "in literal false (expecting 'e')")},
|
||||
|
||||
{"true", "", 0, io.ErrUnexpectedEOF},
|
||||
{"true", "t", 1, io.ErrUnexpectedEOF},
|
||||
{"true", "tr", 2, io.ErrUnexpectedEOF},
|
||||
{"true", "tru", 3, io.ErrUnexpectedEOF},
|
||||
{"true", "true", 4, nil},
|
||||
{"true", "truex", 4, nil},
|
||||
{"true", "x", 0, NewInvalidCharacterError("x", "in literal true (expecting 't')")},
|
||||
{"true", "trux", 3, NewInvalidCharacterError("x", "in literal true (expecting 'e')")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
var got int
|
||||
switch tt.literal {
|
||||
case "null":
|
||||
got = ConsumeNull([]byte(tt.in))
|
||||
case "false":
|
||||
got = ConsumeFalse([]byte(tt.in))
|
||||
case "true":
|
||||
got = ConsumeTrue([]byte(tt.in))
|
||||
default:
|
||||
t.Errorf("invalid literal: %v", tt.literal)
|
||||
}
|
||||
switch {
|
||||
case tt.wantErr == nil && got != tt.want:
|
||||
t.Errorf("Consume%v(%q) = %v, want %v", strings.Title(tt.literal), tt.in, got, tt.want)
|
||||
case tt.wantErr != nil && got != 0:
|
||||
t.Errorf("Consume%v(%q) = %v, want %v", strings.Title(tt.literal), tt.in, got, 0)
|
||||
}
|
||||
|
||||
got, gotErr := ConsumeLiteral([]byte(tt.in), tt.literal)
|
||||
if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Errorf("ConsumeLiteral(%q, %q) = (%v, %v), want (%v, %v)", tt.in, tt.literal, got, gotErr, tt.want, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumeString(t *testing.T) {
|
||||
var errPrev = errors.New("same as previous error")
|
||||
tests := []struct {
|
||||
in string
|
||||
simple bool
|
||||
want int
|
||||
wantUTF8 int // consumed bytes if validateUTF8 is specified
|
||||
wantFlags ValueFlags
|
||||
wantUnquote string
|
||||
wantErr error
|
||||
wantErrUTF8 error // error if validateUTF8 is specified
|
||||
wantErrUnquote error
|
||||
}{
|
||||
{``, false, 0, 0, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"`, false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`""`, true, 2, 2, 0, "", nil, nil, nil},
|
||||
{`""x`, true, 2, 2, 0, "", nil, nil, NewInvalidCharacterError("x", "after string value")},
|
||||
{` ""x`, false, 0, 0, 0, "", NewInvalidCharacterError(" ", "at start of string (expecting '\"')"), errPrev, errPrev},
|
||||
{`"hello`, false, 6, 6, 0, "hello", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"hello"`, true, 7, 7, 0, "hello", nil, nil, nil},
|
||||
{"\"\x00\"", false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidCharacterError("\x00", "in string (expecting non-control character)"), errPrev, errPrev},
|
||||
{`"\u0000"`, false, 8, 8, stringNonVerbatim, "\x00", nil, nil, nil},
|
||||
{"\"\x1f\"", false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidCharacterError("\x1f", "in string (expecting non-control character)"), errPrev, errPrev},
|
||||
{`"\u001f"`, false, 8, 8, stringNonVerbatim, "\x1f", nil, nil, nil},
|
||||
{`"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, true, 54, 54, 0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", nil, nil, nil},
|
||||
{"\" !#$%'()*+,-./0123456789:;=?@[]^_`{|}~\x7f\"", true, 41, 41, 0, " !#$%'()*+,-./0123456789:;=?@[]^_`{|}~\x7f", nil, nil, nil},
|
||||
{`"&"`, false, 3, 3, 0, "&", nil, nil, nil},
|
||||
{`"<"`, false, 3, 3, 0, "<", nil, nil, nil},
|
||||
{`">"`, false, 3, 3, 0, ">", nil, nil, nil},
|
||||
{"\"x\x80\"", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", nil, ErrInvalidUTF8, errPrev},
|
||||
{"\"x\xff\"", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", nil, ErrInvalidUTF8, errPrev},
|
||||
{"\"x\xc0", false, 3, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF},
|
||||
{"\"x\xc0\x80\"", false, 5, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev},
|
||||
{"\"x\xe0", false, 2, 2, 0, "x", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{"\"x\xe0\x80", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF},
|
||||
{"\"x\xe0\x80\x80\"", false, 6, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev},
|
||||
{"\"x\xf0", false, 2, 2, 0, "x", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{"\"x\xf0\x80", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF},
|
||||
{"\"x\xf0\x80\x80", false, 5, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF},
|
||||
{"\"x\xf0\x80\x80\x80\"", false, 7, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev},
|
||||
{"\"x\xed\xba\xad\"", false, 6, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev},
|
||||
{"\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", false, 25, 25, 0, "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", nil, nil, nil},
|
||||
{`"¢"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"¢"`[:3], false, 3, 3, 0, "¢", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote
|
||||
{`"¢"`[:4], false, 4, 4, 0, "¢", nil, nil, nil},
|
||||
{`"€"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"€"`[:3], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"€"`[:4], false, 4, 4, 0, "€", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote
|
||||
{`"€"`[:5], false, 5, 5, 0, "€", nil, nil, nil},
|
||||
{`"𐍈"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"𐍈"`[:3], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"𐍈"`[:4], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"𐍈"`[:5], false, 5, 5, 0, "𐍈", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote
|
||||
{`"𐍈"`[:6], false, 6, 6, 0, "𐍈", nil, nil, nil},
|
||||
{`"x\`, false, 2, 2, stringNonVerbatim, "x", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"x\"`, false, 4, 4, stringNonVerbatim, "x\"", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"x\x"`, false, 2, 2, stringNonVerbatim | stringNonCanonical, "x", NewInvalidEscapeSequenceError(`\x`), errPrev, errPrev},
|
||||
{`"\"\\\b\f\n\r\t"`, false, 16, 16, stringNonVerbatim, "\"\\\b\f\n\r\t", nil, nil, nil},
|
||||
{`"/"`, true, 3, 3, 0, "/", nil, nil, nil},
|
||||
{`"\/"`, false, 4, 4, stringNonVerbatim | stringNonCanonical, "/", nil, nil, nil},
|
||||
{`"\u002f"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "/", nil, nil, nil},
|
||||
{`"\u`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"\uf`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"\uff`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"\ufff`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"\ufffd`, false, 7, 7, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"\ufffd"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "\ufffd", nil, nil, nil},
|
||||
{`"\uABCD"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "\uabcd", nil, nil, nil},
|
||||
{`"\uefX0"`, false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidEscapeSequenceError(`\uefX0`), errPrev, errPrev},
|
||||
{`"\uDEAD`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"\uDEAD"`, false, 8, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", nil, NewInvalidEscapeSequenceError(`\uDEAD"`), errPrev},
|
||||
{`"\uDEAD______"`, false, 14, 1, stringNonVerbatim | stringNonCanonical, "\ufffd______", nil, NewInvalidEscapeSequenceError(`\uDEAD______`), errPrev},
|
||||
{`"\uDEAD\uXXXX"`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", NewInvalidEscapeSequenceError(`\uXXXX`), NewInvalidEscapeSequenceError(`\uDEAD\uXXXX`), NewInvalidEscapeSequenceError(`\uXXXX`)},
|
||||
{`"\uDEAD\uBEEF"`, false, 14, 1, stringNonVerbatim | stringNonCanonical, "\ufffd\ubeef", nil, NewInvalidEscapeSequenceError(`\uDEAD\uBEEF`), errPrev},
|
||||
{`"\uD800\udea`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev},
|
||||
{`"\uD800\udb`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, NewInvalidEscapeSequenceError(`\uD800\udb`), io.ErrUnexpectedEOF},
|
||||
{`"\uD800\udead"`, false, 14, 14, stringNonVerbatim | stringNonCanonical, "\U000102ad", nil, nil, nil},
|
||||
{`"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, false, 50, 50, stringNonVerbatim | stringNonCanonical, "\"\\/\b\f\n\r\t", nil, nil, nil},
|
||||
{`"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`, false, 56, 56, stringNonVerbatim | stringNonCanonical, "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", nil, nil, nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
if tt.wantErrUTF8 == errPrev {
|
||||
tt.wantErrUTF8 = tt.wantErr
|
||||
}
|
||||
if tt.wantErrUnquote == errPrev {
|
||||
tt.wantErrUnquote = tt.wantErrUTF8
|
||||
}
|
||||
|
||||
switch got := ConsumeSimpleString([]byte(tt.in)); {
|
||||
case tt.simple && got != tt.want:
|
||||
t.Errorf("consumeSimpleString(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
case !tt.simple && got != 0:
|
||||
t.Errorf("consumeSimpleString(%q) = %v, want %v", tt.in, got, 0)
|
||||
}
|
||||
|
||||
var gotFlags ValueFlags
|
||||
got, gotErr := ConsumeString(&gotFlags, []byte(tt.in), false)
|
||||
if gotFlags != tt.wantFlags {
|
||||
t.Errorf("consumeString(%q, false) flags = %v, want %v", tt.in, gotFlags, tt.wantFlags)
|
||||
}
|
||||
if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Errorf("consumeString(%q, false) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.want, tt.wantErr)
|
||||
}
|
||||
|
||||
got, gotErr = ConsumeString(&gotFlags, []byte(tt.in), true)
|
||||
if got != tt.wantUTF8 || !reflect.DeepEqual(gotErr, tt.wantErrUTF8) {
|
||||
t.Errorf("consumeString(%q, false) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.wantUTF8, tt.wantErrUTF8)
|
||||
}
|
||||
|
||||
gotUnquote, gotErr := AppendUnquote(nil, tt.in)
|
||||
if string(gotUnquote) != tt.wantUnquote || !reflect.DeepEqual(gotErr, tt.wantErrUnquote) {
|
||||
t.Errorf("AppendUnquote(nil, %q) = (%q, %v), want (%q, %v)", tt.in[:got], gotUnquote, gotErr, tt.wantUnquote, tt.wantErrUnquote)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumeNumber(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
simple bool
|
||||
want int
|
||||
wantErr error
|
||||
}{
|
||||
{"", false, 0, io.ErrUnexpectedEOF},
|
||||
{`"NaN"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")},
|
||||
{`"Infinity"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")},
|
||||
{`"-Infinity"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")},
|
||||
{".0", false, 0, NewInvalidCharacterError(".", "in number (expecting digit)")},
|
||||
{"0", true, 1, nil},
|
||||
{"-0", false, 2, nil},
|
||||
{"+0", false, 0, NewInvalidCharacterError("+", "in number (expecting digit)")},
|
||||
{"1", true, 1, nil},
|
||||
{"-1", false, 2, nil},
|
||||
{"00", true, 1, nil},
|
||||
{"-00", false, 2, nil},
|
||||
{"01", true, 1, nil},
|
||||
{"-01", false, 2, nil},
|
||||
{"0i", true, 1, nil},
|
||||
{"-0i", false, 2, nil},
|
||||
{"0f", true, 1, nil},
|
||||
{"-0f", false, 2, nil},
|
||||
{"9876543210", true, 10, nil},
|
||||
{"-9876543210", false, 11, nil},
|
||||
{"9876543210x", true, 10, nil},
|
||||
{"-9876543210x", false, 11, nil},
|
||||
{" 9876543210", true, 0, NewInvalidCharacterError(" ", "in number (expecting digit)")},
|
||||
{"- 9876543210", false, 1, NewInvalidCharacterError(" ", "in number (expecting digit)")},
|
||||
{strings.Repeat("9876543210", 1000), true, 10000, nil},
|
||||
{"-" + strings.Repeat("9876543210", 1000), false, 1 + 10000, nil},
|
||||
{"0.", false, 1, io.ErrUnexpectedEOF},
|
||||
{"-0.", false, 2, io.ErrUnexpectedEOF},
|
||||
{"0e", false, 1, io.ErrUnexpectedEOF},
|
||||
{"-0e", false, 2, io.ErrUnexpectedEOF},
|
||||
{"0E", false, 1, io.ErrUnexpectedEOF},
|
||||
{"-0E", false, 2, io.ErrUnexpectedEOF},
|
||||
{"0.0", false, 3, nil},
|
||||
{"-0.0", false, 4, nil},
|
||||
{"0e0", false, 3, nil},
|
||||
{"-0e0", false, 4, nil},
|
||||
{"0E0", false, 3, nil},
|
||||
{"-0E0", false, 4, nil},
|
||||
{"0.0123456789", false, 12, nil},
|
||||
{"-0.0123456789", false, 13, nil},
|
||||
{"1.f", false, 2, NewInvalidCharacterError("f", "in number (expecting digit)")},
|
||||
{"-1.f", false, 3, NewInvalidCharacterError("f", "in number (expecting digit)")},
|
||||
{"1.e", false, 2, NewInvalidCharacterError("e", "in number (expecting digit)")},
|
||||
{"-1.e", false, 3, NewInvalidCharacterError("e", "in number (expecting digit)")},
|
||||
{"1e0", false, 3, nil},
|
||||
{"-1e0", false, 4, nil},
|
||||
{"1E0", false, 3, nil},
|
||||
{"-1E0", false, 4, nil},
|
||||
{"1Ex", false, 2, NewInvalidCharacterError("x", "in number (expecting digit)")},
|
||||
{"-1Ex", false, 3, NewInvalidCharacterError("x", "in number (expecting digit)")},
|
||||
{"1e-0", false, 4, nil},
|
||||
{"-1e-0", false, 5, nil},
|
||||
{"1e+0", false, 4, nil},
|
||||
{"-1e+0", false, 5, nil},
|
||||
{"1E-0", false, 4, nil},
|
||||
{"-1E-0", false, 5, nil},
|
||||
{"1E+0", false, 4, nil},
|
||||
{"-1E+0", false, 5, nil},
|
||||
{"1E+00500", false, 8, nil},
|
||||
{"-1E+00500", false, 9, nil},
|
||||
{"1E+00500x", false, 8, nil},
|
||||
{"-1E+00500x", false, 9, nil},
|
||||
{"9876543210.0123456789e+01234589x", false, 31, nil},
|
||||
{"-9876543210.0123456789e+01234589x", false, 32, nil},
|
||||
{"1_000_000", true, 1, nil},
|
||||
{"0x12ef", true, 1, nil},
|
||||
{"0x1p-2", true, 1, nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
switch got := ConsumeSimpleNumber([]byte(tt.in)); {
|
||||
case tt.simple && got != tt.want:
|
||||
t.Errorf("ConsumeSimpleNumber(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
case !tt.simple && got != 0:
|
||||
t.Errorf("ConsumeSimpleNumber(%q) = %v, want %v", tt.in, got, 0)
|
||||
}
|
||||
|
||||
got, gotErr := ConsumeNumber([]byte(tt.in))
|
||||
if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Errorf("ConsumeNumber(%q) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.want, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHexUint16(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want uint16
|
||||
wantOk bool
|
||||
}{
|
||||
{"", 0, false},
|
||||
{"a", 0, false},
|
||||
{"ab", 0, false},
|
||||
{"abc", 0, false},
|
||||
{"abcd", 0xabcd, true},
|
||||
{"abcde", 0, false},
|
||||
{"9eA1", 0x9ea1, true},
|
||||
{"gggg", 0, false},
|
||||
{"0000", 0x0000, true},
|
||||
{"1234", 0x1234, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got, gotOk := parseHexUint16([]byte(tt.in))
|
||||
if got != tt.want || gotOk != tt.wantOk {
|
||||
t.Errorf("parseHexUint16(%q) = (0x%04x, %v), want (0x%04x, %v)", tt.in, got, gotOk, tt.want, tt.wantOk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseUint(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want uint64
|
||||
wantOk bool
|
||||
}{
|
||||
{"", 0, false},
|
||||
{"0", 0, true},
|
||||
{"1", 1, true},
|
||||
{"-1", 0, false},
|
||||
{"1f", 0, false},
|
||||
{"00", 0, false},
|
||||
{"01", 0, false},
|
||||
{"10", 10, true},
|
||||
{"10.9", 0, false},
|
||||
{" 10", 0, false},
|
||||
{"10 ", 0, false},
|
||||
{"123456789", 123456789, true},
|
||||
{"123456789d", 0, false},
|
||||
{"18446744073709551614", math.MaxUint64 - 1, true},
|
||||
{"18446744073709551615", math.MaxUint64, true},
|
||||
{"18446744073709551616", math.MaxUint64, false},
|
||||
{"18446744073709551620", math.MaxUint64, false},
|
||||
{"18446744073709551700", math.MaxUint64, false},
|
||||
{"18446744073709552000", math.MaxUint64, false},
|
||||
{"18446744073709560000", math.MaxUint64, false},
|
||||
{"18446744073709600000", math.MaxUint64, false},
|
||||
{"18446744073710000000", math.MaxUint64, false},
|
||||
{"18446744073800000000", math.MaxUint64, false},
|
||||
{"18446744074000000000", math.MaxUint64, false},
|
||||
{"18446744080000000000", math.MaxUint64, false},
|
||||
{"18446744100000000000", math.MaxUint64, false},
|
||||
{"18446745000000000000", math.MaxUint64, false},
|
||||
{"18446750000000000000", math.MaxUint64, false},
|
||||
{"18446800000000000000", math.MaxUint64, false},
|
||||
{"18447000000000000000", math.MaxUint64, false},
|
||||
{"18450000000000000000", math.MaxUint64, false},
|
||||
{"18500000000000000000", math.MaxUint64, false},
|
||||
{"19000000000000000000", math.MaxUint64, false},
|
||||
{"19999999999999999999", math.MaxUint64, false},
|
||||
{"20000000000000000000", math.MaxUint64, false},
|
||||
{"100000000000000000000", math.MaxUint64, false},
|
||||
{"99999999999999999999999999999999", math.MaxUint64, false},
|
||||
{"99999999999999999999999999999999f", 0, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got, gotOk := ParseUint([]byte(tt.in))
|
||||
if got != tt.want || gotOk != tt.wantOk {
|
||||
t.Errorf("ParseUint(%q) = (%v, %v), want (%v, %v)", tt.in, got, gotOk, tt.want, tt.wantOk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFloat(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want32 float64
|
||||
want64 float64
|
||||
wantOk bool
|
||||
}{
|
||||
{"0", 0, 0, true},
|
||||
{"-1", -1, -1, true},
|
||||
{"1", 1, 1, true},
|
||||
|
||||
{"-16777215", -16777215, -16777215, true}, // -(1<<24 - 1)
|
||||
{"16777215", 16777215, 16777215, true}, // +(1<<24 - 1)
|
||||
{"-16777216", -16777216, -16777216, true}, // -(1<<24)
|
||||
{"16777216", 16777216, 16777216, true}, // +(1<<24)
|
||||
{"-16777217", -16777216, -16777217, true}, // -(1<<24 + 1)
|
||||
{"16777217", 16777216, 16777217, true}, // +(1<<24 + 1)
|
||||
|
||||
{"-9007199254740991", -9007199254740992, -9007199254740991, true}, // -(1<<53 - 1)
|
||||
{"9007199254740991", 9007199254740992, 9007199254740991, true}, // +(1<<53 - 1)
|
||||
{"-9007199254740992", -9007199254740992, -9007199254740992, true}, // -(1<<53)
|
||||
{"9007199254740992", 9007199254740992, 9007199254740992, true}, // +(1<<53)
|
||||
{"-9007199254740993", -9007199254740992, -9007199254740992, true}, // -(1<<53 + 1)
|
||||
{"9007199254740993", 9007199254740992, 9007199254740992, true}, // +(1<<53 + 1)
|
||||
|
||||
{"-1e1000", -math.MaxFloat32, -math.MaxFloat64, false},
|
||||
{"1e1000", +math.MaxFloat32, +math.MaxFloat64, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got32, gotOk32 := ParseFloat([]byte(tt.in), 32)
|
||||
if got32 != tt.want32 || gotOk32 != tt.wantOk {
|
||||
t.Errorf("ParseFloat(%q, 32) = (%v, %v), want (%v, %v)", tt.in, got32, gotOk32, tt.want32, tt.wantOk)
|
||||
}
|
||||
|
||||
got64, gotOk64 := ParseFloat([]byte(tt.in), 64)
|
||||
if got64 != tt.want64 || gotOk64 != tt.wantOk {
|
||||
t.Errorf("ParseFloat(%q, 64) = (%v, %v), want (%v, %v)", tt.in, got64, gotOk64, tt.want64, tt.wantOk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -1,737 +0,0 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"encoding/json/internal/jsonflags"
|
||||
"encoding/json/internal/jsontest"
|
||||
"encoding/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// TestEncoder tests whether we can produce JSON with either tokens or raw values.
|
||||
func TestEncoder(t *testing.T) {
|
||||
for _, td := range coderTestdata {
|
||||
for _, formatName := range []string{"Compact", "Indented"} {
|
||||
for _, typeName := range []string{"Token", "Value", "TokenDelims"} {
|
||||
t.Run(path.Join(td.name.Name, typeName, formatName), func(t *testing.T) {
|
||||
testEncoder(t, td.name.Where, formatName, typeName, td)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func testEncoder(t *testing.T, where jsontest.CasePos, formatName, typeName string, td coderTestdataEntry) {
|
||||
var want string
|
||||
var opts []Options
|
||||
dst := new(bytes.Buffer)
|
||||
opts = append(opts, jsonflags.OmitTopLevelNewline|1)
|
||||
want = td.outCompacted
|
||||
switch formatName {
|
||||
case "Indented":
|
||||
opts = append(opts, Multiline(true))
|
||||
opts = append(opts, WithIndentPrefix("\t"))
|
||||
opts = append(opts, WithIndent(" "))
|
||||
if td.outIndented != "" {
|
||||
want = td.outIndented
|
||||
}
|
||||
}
|
||||
enc := NewEncoder(dst, opts...)
|
||||
|
||||
switch typeName {
|
||||
case "Token":
|
||||
var pointers []Pointer
|
||||
for _, tok := range td.tokens {
|
||||
if err := enc.WriteToken(tok); err != nil {
|
||||
t.Fatalf("%s: Encoder.WriteToken error: %v", where, err)
|
||||
}
|
||||
if td.pointers != nil {
|
||||
pointers = append(pointers, enc.StackPointer())
|
||||
}
|
||||
}
|
||||
if !slices.Equal(pointers, td.pointers) {
|
||||
t.Fatalf("%s: pointers mismatch:\ngot %q\nwant %q", where, pointers, td.pointers)
|
||||
}
|
||||
case "Value":
|
||||
if err := enc.WriteValue(Value(td.in)); err != nil {
|
||||
t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
|
||||
}
|
||||
case "TokenDelims":
|
||||
// Use WriteToken for object/array delimiters, WriteValue otherwise.
|
||||
for _, tok := range td.tokens {
|
||||
switch tok.Kind() {
|
||||
case '{', '}', '[', ']':
|
||||
if err := enc.WriteToken(tok); err != nil {
|
||||
t.Fatalf("%s: Encoder.WriteToken error: %v", where, err)
|
||||
}
|
||||
default:
|
||||
val := Value(tok.String())
|
||||
if tok.Kind() == '"' {
|
||||
val, _ = jsonwire.AppendQuote(nil, tok.String(), &jsonflags.Flags{})
|
||||
}
|
||||
if err := enc.WriteValue(val); err != nil {
|
||||
t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
got := dst.String()
|
||||
if got != want {
|
||||
t.Errorf("%s: output mismatch:\ngot %q\nwant %q", where, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFaultyEncoder tests that temporary I/O errors are not fatal.
|
||||
func TestFaultyEncoder(t *testing.T) {
|
||||
for _, td := range coderTestdata {
|
||||
for _, typeName := range []string{"Token", "Value"} {
|
||||
t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) {
|
||||
testFaultyEncoder(t, td.name.Where, typeName, td)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
func testFaultyEncoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) {
|
||||
b := &FaultyBuffer{
|
||||
MaxBytes: 1,
|
||||
MayError: io.ErrShortWrite,
|
||||
}
|
||||
|
||||
// Write all the tokens.
|
||||
// Even if the underlying io.Writer may be faulty,
|
||||
// writing a valid token or value is guaranteed to at least
|
||||
// be appended to the internal buffer.
|
||||
// In other words, syntactic errors occur before I/O errors.
|
||||
enc := NewEncoder(b)
|
||||
switch typeName {
|
||||
case "Token":
|
||||
for i, tok := range td.tokens {
|
||||
err := enc.WriteToken(tok)
|
||||
if err != nil && !errors.Is(err, io.ErrShortWrite) {
|
||||
t.Fatalf("%s: %d: Encoder.WriteToken error: %v", where, i, err)
|
||||
}
|
||||
}
|
||||
case "Value":
|
||||
err := enc.WriteValue(Value(td.in))
|
||||
if err != nil && !errors.Is(err, io.ErrShortWrite) {
|
||||
t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
|
||||
}
|
||||
}
|
||||
gotOutput := string(append(b.B, enc.s.unflushedBuffer()...))
|
||||
wantOutput := td.outCompacted + "\n"
|
||||
if gotOutput != wantOutput {
|
||||
t.Fatalf("%s: output mismatch:\ngot %s\nwant %s", where, gotOutput, wantOutput)
|
||||
}
|
||||
}
|
||||
|
||||
type encoderMethodCall struct {
|
||||
in tokOrVal
|
||||
wantErr error
|
||||
wantPointer Pointer
|
||||
}
|
||||
|
||||
var encoderErrorTestdata = []struct {
|
||||
name jsontest.CaseName
|
||||
opts []Options
|
||||
calls []encoderMethodCall
|
||||
wantOut string
|
||||
}{{
|
||||
name: jsontest.Name("InvalidToken"),
|
||||
calls: []encoderMethodCall{
|
||||
{zeroToken, E(errInvalidToken), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidValue"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`#`), newInvalidCharacterError("#", "at start of value"), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidValue/DoubleZero"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`00`), newInvalidCharacterError("0", "after top-level value").withPos(`0`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedValue"),
|
||||
calls: []encoderMethodCall{
|
||||
{zeroValue, E(io.ErrUnexpectedEOF).withPos("", ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedNull"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`nul`), E(io.ErrUnexpectedEOF).withPos("nul", ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidNull"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`nulL`), newInvalidCharacterError("L", "in literal null (expecting 'l')").withPos(`nul`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedFalse"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`fals`), E(io.ErrUnexpectedEOF).withPos("fals", ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidFalse"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`falsE`), newInvalidCharacterError("E", "in literal false (expecting 'e')").withPos(`fals`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedTrue"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`tru`), E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidTrue"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`truE`), newInvalidCharacterError("E", "in literal true (expecting 'e')").withPos(`tru`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedString"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`"star`), E(io.ErrUnexpectedEOF).withPos(`"star`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidString"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`"ok` + "\x00"), newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"),
|
||||
opts: []Options{AllowInvalidUTF8(true)},
|
||||
calls: []encoderMethodCall{
|
||||
{String("living\xde\xad\xbe\xef"), nil, ""},
|
||||
},
|
||||
wantOut: "\"living\xde\xad\ufffd\ufffd\"\n",
|
||||
}, {
|
||||
name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"),
|
||||
opts: []Options{AllowInvalidUTF8(true)},
|
||||
calls: []encoderMethodCall{
|
||||
{Value("\"living\xde\xad\xbe\xef\""), nil, ""},
|
||||
},
|
||||
wantOut: "\"living\xde\xad\ufffd\ufffd\"\n",
|
||||
}, {
|
||||
name: jsontest.Name("InvalidString/RejectInvalidUTF8"),
|
||||
opts: []Options{AllowInvalidUTF8(false)},
|
||||
calls: []encoderMethodCall{
|
||||
{String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8), ""},
|
||||
{Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("name"), nil, ""},
|
||||
{BeginArray, nil, ""},
|
||||
{String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8).withPos(`{"name":[`, "/name/0"), ""},
|
||||
{Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("{\"name\":[\"living\xde\xad", "/name/0"), ""},
|
||||
},
|
||||
wantOut: `{"name":[`,
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedNumber"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`0.`), E(io.ErrUnexpectedEOF).withPos("0", ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidNumber"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`0.e`), newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedObject/AfterStart"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`{`), E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedObject/AfterName"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`{"X"`), E(io.ErrUnexpectedEOF).withPos(`{"X"`, "/X"), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedObject/AfterColon"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`{"X":`), E(io.ErrUnexpectedEOF).withPos(`{"X":`, "/X"), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedObject/AfterValue"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`{"0":0`), E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedObject/AfterComma"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`{"0":0,`), E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidObject/MissingColon"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "fizz" "buzz" } `), newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
|
||||
{Value(` { "fizz" , "buzz" } `), newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidObject/MissingComma"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "fizz" : "buzz" "gazz" } `), newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
|
||||
{Value(` { "fizz" : "buzz" : "gazz" } `), newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidObject/ExtraComma"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { , } `), newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""},
|
||||
{Value(` { "fizz" : "buzz" , } `), newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidObject/InvalidName"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`{ null }`), newInvalidCharacterError("n", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
|
||||
{Value(`{ false }`), newInvalidCharacterError("f", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
|
||||
{Value(`{ true }`), newInvalidCharacterError("t", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
|
||||
{Value(`{ 0 }`), newInvalidCharacterError("0", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
|
||||
{Value(`{ {} }`), newInvalidCharacterError("{", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
|
||||
{Value(`{ [] }`), newInvalidCharacterError("[", `at start of string (expecting '"')`).withPos(`{ `, ""), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{Null, E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{Value(`null`), E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{False, E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{Value(`false`), E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{True, E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{Value(`true`), E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{Uint(0), E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{Value(`0`), E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{BeginObject, E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{Value(`{}`), E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{BeginArray, E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{Value(`[]`), E(ErrNonStringName).withPos(`{`, ""), ""},
|
||||
{EndObject, nil, ""},
|
||||
},
|
||||
wantOut: "{}\n",
|
||||
}, {
|
||||
name: jsontest.Name("InvalidObject/InvalidValue"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`{ "0": x }`), newInvalidCharacterError("x", `at start of value`).withPos(`{ "0": `, "/0"), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidObject/MismatchingDelim"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { ] `), newInvalidCharacterError("]", `at start of string (expecting '"')`).withPos(` { `, ""), ""},
|
||||
{Value(` { "0":0 ] `), newInvalidCharacterError("]", `after object value (expecting ',' or '}')`).withPos(` { "0":0 `, ""), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{EndArray, E(errMismatchDelim).withPos(`{`, ""), ""},
|
||||
{Value(`]`), newInvalidCharacterError("]", "at start of value").withPos(`{`, ""), ""},
|
||||
{EndObject, nil, ""},
|
||||
},
|
||||
wantOut: "{}\n",
|
||||
}, {
|
||||
name: jsontest.Name("ValidObject/UniqueNames"),
|
||||
calls: []encoderMethodCall{
|
||||
{BeginObject, nil, ""},
|
||||
{String("0"), nil, ""},
|
||||
{Uint(0), nil, ""},
|
||||
{String("1"), nil, ""},
|
||||
{Uint(1), nil, ""},
|
||||
{EndObject, nil, ""},
|
||||
{Value(` { "0" : 0 , "1" : 1 } `), nil, ""},
|
||||
},
|
||||
wantOut: `{"0":0,"1":1}` + "\n" + `{"0":0,"1":1}` + "\n",
|
||||
}, {
|
||||
name: jsontest.Name("ValidObject/DuplicateNames"),
|
||||
opts: []Options{AllowDuplicateNames(true)},
|
||||
calls: []encoderMethodCall{
|
||||
{BeginObject, nil, ""},
|
||||
{String("0"), nil, ""},
|
||||
{Uint(0), nil, ""},
|
||||
{String("0"), nil, ""},
|
||||
{Uint(0), nil, ""},
|
||||
{EndObject, nil, ""},
|
||||
{Value(` { "0" : 0 , "0" : 0 } `), nil, ""},
|
||||
},
|
||||
wantOut: `{"0":0,"0":0}` + "\n" + `{"0":0,"0":0}` + "\n",
|
||||
}, {
|
||||
name: jsontest.Name("InvalidObject/DuplicateNames"),
|
||||
calls: []encoderMethodCall{
|
||||
{BeginObject, nil, ""},
|
||||
{String("X"), nil, ""},
|
||||
{BeginObject, nil, ""},
|
||||
{EndObject, nil, ""},
|
||||
{String("X"), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"},
|
||||
{Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"},
|
||||
{String("Y"), nil, ""},
|
||||
{BeginObject, nil, ""},
|
||||
{EndObject, nil, ""},
|
||||
{String("X"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"},
|
||||
{Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"},
|
||||
{String("Y"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"},
|
||||
{Value(`"Y"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"},
|
||||
{EndObject, nil, ""},
|
||||
{Value(` { "X" : 0 , "Y" : 1 , "X" : 0 } `), E(ErrDuplicateName).withPos(`{"X":{},"Y":{}}`+"\n"+` { "X" : 0 , "Y" : 1 , `, "/X"), ""},
|
||||
},
|
||||
wantOut: `{"X":{},"Y":{}}` + "\n",
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedArray/AfterStart"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`[`), E(io.ErrUnexpectedEOF).withPos(`[`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedArray/AfterValue"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`[0`), E(io.ErrUnexpectedEOF).withPos(`[0`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedArray/AfterComma"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`[0,`), E(io.ErrUnexpectedEOF).withPos(`[0,`, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("TruncatedArray/MissingComma"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ "fizz" "buzz" ] `), newInvalidCharacterError("\"", "after array value (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("InvalidArray/MismatchingDelim"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ } `), newInvalidCharacterError("}", `at start of value`).withPos(` [ `, "/0"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{EndObject, E(errMismatchDelim).withPos(`[`, "/0"), ""},
|
||||
{Value(`}`), newInvalidCharacterError("}", "at start of value").withPos(`[`, "/0"), ""},
|
||||
{EndArray, nil, ""},
|
||||
},
|
||||
wantOut: "[]\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/Object/SpaceAfterColon"),
|
||||
opts: []Options{SpaceAfterColon(true)},
|
||||
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
|
||||
wantOut: "{\"fizz\": \"buzz\",\"wizz\": \"wuzz\"}\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/Object/SpaceAfterComma"),
|
||||
opts: []Options{SpaceAfterComma(true)},
|
||||
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
|
||||
wantOut: "{\"fizz\":\"buzz\", \"wizz\":\"wuzz\"}\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/Object/SpaceAfterColonAndComma"),
|
||||
opts: []Options{SpaceAfterColon(true), SpaceAfterComma(true)},
|
||||
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
|
||||
wantOut: "{\"fizz\": \"buzz\", \"wizz\": \"wuzz\"}\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/Object/NoSpaceAfterColon+SpaceAfterComma+Multiline"),
|
||||
opts: []Options{SpaceAfterColon(false), SpaceAfterComma(true), Multiline(true)},
|
||||
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
|
||||
wantOut: "{\n\t\"fizz\":\"buzz\", \n\t\"wizz\":\"wuzz\"\n}\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/Array/SpaceAfterComma"),
|
||||
opts: []Options{SpaceAfterComma(true)},
|
||||
calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}},
|
||||
wantOut: "[\"fizz\", \"buzz\"]\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/Array/NoSpaceAfterComma+Multiline"),
|
||||
opts: []Options{SpaceAfterComma(false), Multiline(true)},
|
||||
calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}},
|
||||
wantOut: "[\n\t\"fizz\",\n\t\"buzz\"\n]\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/ReorderWithWhitespace"),
|
||||
opts: []Options{
|
||||
AllowDuplicateNames(true),
|
||||
AllowInvalidUTF8(true),
|
||||
ReorderRawObjects(true),
|
||||
SpaceAfterComma(true),
|
||||
SpaceAfterColon(false),
|
||||
Multiline(true),
|
||||
WithIndentPrefix(" "),
|
||||
WithIndent("\t"),
|
||||
PreserveRawStrings(true),
|
||||
},
|
||||
calls: []encoderMethodCall{
|
||||
{BeginArray, nil, ""},
|
||||
{BeginArray, nil, ""},
|
||||
{Value(` { "fizz" : "buzz" ,
|
||||
"zip" : {
|
||||
"x` + "\xfd" + `x" : 123 , "x` + "\xff" + `x" : 123, "x` + "\xfe" + `x" : 123
|
||||
},
|
||||
"zap" : {
|
||||
"xxx" : 333, "xxx": 1, "xxx": 22
|
||||
},
|
||||
"alpha" : "bravo" } `), nil, ""},
|
||||
{EndArray, nil, ""},
|
||||
{EndArray, nil, ""},
|
||||
},
|
||||
wantOut: "[\n \t[\n \t\t{\n \t\t\t\"alpha\":\"bravo\", \n \t\t\t\"fizz\":\"buzz\", \n \t\t\t\"zap\":{\n \t\t\t\t\"xxx\":1, \n \t\t\t\t\"xxx\":22, \n \t\t\t\t\"xxx\":333\n \t\t\t}, \n \t\t\t\"zip\":{\n \t\t\t\t\"x\xfdx\":123, \n \t\t\t\t\"x\xfex\":123, \n \t\t\t\t\"x\xffx\":123\n \t\t\t}\n \t\t}\n \t]\n ]\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/CanonicalizeRawInts"),
|
||||
opts: []Options{CanonicalizeRawInts(true), SpaceAfterComma(true)},
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""},
|
||||
},
|
||||
wantOut: "[0.100, 5.0, 1E6, -9223372036854776000, -10, -1, 0, 0, 1, 10, 9223372036854776000]\n",
|
||||
}, {
|
||||
name: jsontest.Name("Format/CanonicalizeRawFloats"),
|
||||
opts: []Options{CanonicalizeRawFloats(true), SpaceAfterComma(true)},
|
||||
calls: []encoderMethodCall{
|
||||
{Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""},
|
||||
},
|
||||
wantOut: "[0.1, 5, 1000000, -9223372036854775808, -10, -1, 0, 0, 1, 10, 9223372036854775807]\n",
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` "a` + "\xff" + `0" `), E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""},
|
||||
{String(`a` + "\xff" + `0`), E(jsonwire.ErrInvalidUTF8).withPos(``, ""), ""},
|
||||
},
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/0"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ "a` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`[ "a`, "/0"), ""},
|
||||
{String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`[`, "/0"), ""},
|
||||
},
|
||||
wantOut: `[`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/1"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ "a1" , "b` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{String("a1"), nil, ""},
|
||||
{Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", "b`, "/1"), ""},
|
||||
{String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",`, "/1"), ""},
|
||||
},
|
||||
wantOut: `["a1"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/0/0"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a`, "/0/0"), ""},
|
||||
{BeginArray, nil, "/0"},
|
||||
{Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[[ "a`, "/0/0"), "/0"},
|
||||
{String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[[`, "/0/0"), "/0"},
|
||||
},
|
||||
wantOut: `[[`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/1/0"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ "a1" , [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{String("a1"), nil, "/0"},
|
||||
{Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a`, "/1/0"), ""},
|
||||
{BeginArray, nil, "/1"},
|
||||
{Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[ "a`, "/1/0"), "/1"},
|
||||
{String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[`, "/1/0"), "/1"},
|
||||
},
|
||||
wantOut: `["a1",[`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/0/1"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a2" , "b`, "/0/1"), ""},
|
||||
{BeginArray, nil, "/0"},
|
||||
{String("a2"), nil, "/0/0"},
|
||||
{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2", "b`, "/0/1"), "/0/0"},
|
||||
{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2",`, "/0/1"), "/0/0"},
|
||||
},
|
||||
wantOut: `[["a2"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/1/1"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{String("a1"), nil, "/0"},
|
||||
{Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a2" , "b`, "/1/1"), ""},
|
||||
{BeginArray, nil, "/1"},
|
||||
{String("a2"), nil, "/1/0"},
|
||||
{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2", "b`, "/1/1"), "/1/0"},
|
||||
{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2",`, "/1/1"), "/1/0"},
|
||||
},
|
||||
wantOut: `["a1",["a2"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/a1-"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a` + "\xff" + `1" : "b1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{ "a`, ""), ""},
|
||||
{String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{`, ""), ""},
|
||||
},
|
||||
wantOut: `{`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/a1"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : "b` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": "b`, "/a1"), ""},
|
||||
{String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":`, "/a1"), ""},
|
||||
},
|
||||
wantOut: `{"a1"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/c1-"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{String("b1"), nil, "/a1"},
|
||||
{Value(` "c` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1": "c`, ""), "/a1"},
|
||||
{String(`c` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":`, ""), "/a1"},
|
||||
},
|
||||
wantOut: `{"a1":"b1"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/c1"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{String("b1"), nil, "/a1"},
|
||||
{String("c1"), nil, "/c1"},
|
||||
{Value(` "d` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1": "d`, "/c1"), "/c1"},
|
||||
{String(`d` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1":`, "/c1"), "/c1"},
|
||||
},
|
||||
wantOut: `{"a1":"b1","c1"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/a1/a2-"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : { "a` + "\xff" + `2" : "b2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{Value(` { "a` + "\xff" + `2" : "b2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a`, "/a1"), ""},
|
||||
{BeginObject, nil, "/a1"},
|
||||
{Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{ "a`, "/a1"), "/a1"},
|
||||
{String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{`, "/a1"), "/a1"},
|
||||
},
|
||||
wantOut: `{"a1":{`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/a1/a2"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : { "a2" : "b` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b`, "/a1/a2"), ""},
|
||||
{BeginObject, nil, "/a1"},
|
||||
{String("a2"), nil, "/a1/a2"},
|
||||
{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2": "b`, "/a1/a2"), "/a1/a2"},
|
||||
{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":`, "/a1/a2"), "/a1/a2"},
|
||||
},
|
||||
wantOut: `{"a1":{"a2"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/a1/c2-"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{BeginObject, nil, "/a1"},
|
||||
{String("a2"), nil, "/a1/a2"},
|
||||
{String("b2"), nil, "/a1/a2"},
|
||||
{Value(` "c` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2", "c`, "/a1"), "/a1/a2"},
|
||||
{String(`c` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2",`, "/a1"), "/a1/a2"},
|
||||
},
|
||||
wantOut: `{"a1":{"a2":"b2"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/a1/c2"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{Value(` { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a2"), nil, "/a1/a2"},
|
||||
{String("b2"), nil, "/a1/a2"},
|
||||
{String("c2"), nil, "/a1/c2"},
|
||||
{Value(` "d` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2": "d`, "/a1/c2"), "/a1/c2"},
|
||||
{String(`d` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2":`, "/a1/c2"), "/a1/c2"},
|
||||
},
|
||||
wantOut: `{"a1":{"a2":"b2","c2"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/1/a2"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{String("a1"), nil, "/0"},
|
||||
{Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", { "a2" : "b`, "/1/a2"), ""},
|
||||
{BeginObject, nil, "/1"},
|
||||
{String("a2"), nil, "/1/a2"},
|
||||
{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2": "b`, "/1/a2"), "/1/a2"},
|
||||
{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2":`, "/1/a2"), "/1/a2"},
|
||||
},
|
||||
wantOut: `["a1",{"a2"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/c1/1"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""},
|
||||
{BeginObject, nil, ""},
|
||||
{String("a1"), nil, "/a1"},
|
||||
{String("b1"), nil, "/a1"},
|
||||
{String("c1"), nil, "/c1"},
|
||||
{Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1": [ "a2" , "b`, "/c1/1"), ""},
|
||||
{BeginArray, nil, "/c1"},
|
||||
{String("a2"), nil, "/c1/0"},
|
||||
{Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2", "b`, "/c1/1"), "/c1/0"},
|
||||
{String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2",`, "/c1/1"), "/c1/0"},
|
||||
},
|
||||
wantOut: `{"a1":"b1","c1":["a2"`,
|
||||
}, {
|
||||
name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"),
|
||||
calls: []encoderMethodCall{
|
||||
{Value(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{Value(` { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
|
||||
{BeginObject, nil, "/0"},
|
||||
{String("a1"), nil, "/0/a1"},
|
||||
{Value(` [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1": [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
|
||||
{BeginArray, nil, ""},
|
||||
{String("a2"), nil, "/0/a1/0"},
|
||||
{Value(` { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2", { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
|
||||
{BeginObject, nil, "/0/a1/1"},
|
||||
{String("a3"), nil, "/0/a1/1/a3"},
|
||||
{String("b3"), nil, "/0/a1/1/a3"},
|
||||
{String("c3"), nil, "/0/a1/1/c3"},
|
||||
{Value(` [ "a4" , "b` + "\xff" + `4" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3": [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
|
||||
{BeginArray, nil, "/0/a1/1/c3"},
|
||||
{String("a4"), nil, "/0/a1/1/c3/0"},
|
||||
{Value(` "b` + "\xff" + `4" `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4", "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
|
||||
{String(`b` + "\xff" + `4`), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4",`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
|
||||
},
|
||||
wantOut: `[{"a1":["a2",{"a3":"b3","c3":["a4"`,
|
||||
}}
|
||||
|
||||
// TestEncoderErrors test that Encoder errors occur when we expect and
|
||||
// leaves the Encoder in a consistent state.
|
||||
func TestEncoderErrors(t *testing.T) {
|
||||
for _, td := range encoderErrorTestdata {
|
||||
t.Run(path.Join(td.name.Name), func(t *testing.T) {
|
||||
testEncoderErrors(t, td.name.Where, td.opts, td.calls, td.wantOut)
|
||||
})
|
||||
}
|
||||
}
|
||||
func testEncoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, calls []encoderMethodCall, wantOut string) {
|
||||
dst := new(bytes.Buffer)
|
||||
enc := NewEncoder(dst, opts...)
|
||||
for i, call := range calls {
|
||||
var gotErr error
|
||||
switch tokVal := call.in.(type) {
|
||||
case Token:
|
||||
gotErr = enc.WriteToken(tokVal)
|
||||
case Value:
|
||||
gotErr = enc.WriteValue(tokVal)
|
||||
}
|
||||
if !equalError(gotErr, call.wantErr) {
|
||||
t.Fatalf("%s: %d: error mismatch:\ngot %v\nwant %v", where, i, gotErr, call.wantErr)
|
||||
}
|
||||
if call.wantPointer != "" {
|
||||
gotPointer := enc.StackPointer()
|
||||
if gotPointer != call.wantPointer {
|
||||
t.Fatalf("%s: %d: Encoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
gotOut := dst.String() + string(enc.s.unflushedBuffer())
|
||||
if gotOut != wantOut {
|
||||
t.Fatalf("%s: output mismatch:\ngot %q\nwant %q", where, gotOut, wantOut)
|
||||
}
|
||||
gotOffset := int(enc.OutputOffset())
|
||||
wantOffset := len(wantOut)
|
||||
if gotOffset != wantOffset {
|
||||
t.Fatalf("%s: Encoder.OutputOffset = %v, want %v", where, gotOffset, wantOffset)
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsontext_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"encoding/json/jsontext"
|
||||
"encoding/json/v2"
|
||||
)
|
||||
|
||||
// This example demonstrates the use of the [Encoder] and [Decoder] to
|
||||
// parse and modify JSON without unmarshaling it into a concrete Go type.
|
||||
func Example_stringReplace() {
|
||||
// Example input with non-idiomatic use of "Golang" instead of "Go".
|
||||
const input = `{
|
||||
"title": "Golang version 1 is released",
|
||||
"author": "Andrew Gerrand",
|
||||
"date": "2012-03-28",
|
||||
"text": "Today marks a major milestone in the development of the Golang programming language.",
|
||||
"otherArticles": [
|
||||
"Twelve Years of Golang",
|
||||
"The Laws of Reflection",
|
||||
"Learn Golang from your browser"
|
||||
]
|
||||
}`
|
||||
|
||||
// Using a Decoder and Encoder, we can parse through every token,
|
||||
// check and modify the token if necessary, and
|
||||
// write the token to the output.
|
||||
var replacements []jsontext.Pointer
|
||||
in := strings.NewReader(input)
|
||||
dec := jsontext.NewDecoder(in)
|
||||
out := new(bytes.Buffer)
|
||||
enc := jsontext.NewEncoder(out, jsontext.Multiline(true)) // expand for readability
|
||||
for {
|
||||
// Read a token from the input.
|
||||
tok, err := dec.ReadToken()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Check whether the token contains the string "Golang" and
|
||||
// replace each occurrence with "Go" instead.
|
||||
if tok.Kind() == '"' && strings.Contains(tok.String(), "Golang") {
|
||||
replacements = append(replacements, dec.StackPointer())
|
||||
tok = jsontext.String(strings.ReplaceAll(tok.String(), "Golang", "Go"))
|
||||
}
|
||||
|
||||
// Write the (possibly modified) token to the output.
|
||||
if err := enc.WriteToken(tok); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Print the list of replacements and the adjusted JSON output.
|
||||
if len(replacements) > 0 {
|
||||
fmt.Println(`Replaced "Golang" with "Go" in:`)
|
||||
for _, where := range replacements {
|
||||
fmt.Println("\t" + where)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println("Result:", out.String())
|
||||
|
||||
// Output:
|
||||
// Replaced "Golang" with "Go" in:
|
||||
// /title
|
||||
// /text
|
||||
// /otherArticles/0
|
||||
// /otherArticles/2
|
||||
//
|
||||
// Result: {
|
||||
// "title": "Go version 1 is released",
|
||||
// "author": "Andrew Gerrand",
|
||||
// "date": "2012-03-28",
|
||||
// "text": "Today marks a major milestone in the development of the Go programming language.",
|
||||
// "otherArticles": [
|
||||
// "Twelve Years of Go",
|
||||
// "The Laws of Reflection",
|
||||
// "Learn Go from your browser"
|
||||
// ]
|
||||
// }
|
||||
}
|
||||
|
||||
// Directly embedding JSON within HTML requires special handling for safety.
|
||||
// Escape certain runes to prevent JSON directly treated as HTML
|
||||
// from being able to perform <script> injection.
|
||||
//
|
||||
// This example shows how to obtain equivalent behavior provided by the
|
||||
// v1 [encoding/json] package that is no longer directly supported by this package.
|
||||
// Newly written code that intermix JSON and HTML should instead be using the
|
||||
// [github.com/google/safehtml] module for safety purposes.
|
||||
func ExampleEscapeForHTML() {
|
||||
page := struct {
|
||||
Title string
|
||||
Body string
|
||||
}{
|
||||
Title: "Example Embedded Javascript",
|
||||
Body: `<script> console.log("Hello, world!"); </script>`,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(&page,
|
||||
// Escape certain runes within a JSON string so that
|
||||
// JSON will be safe to directly embed inside HTML.
|
||||
jsontext.EscapeForHTML(true),
|
||||
jsontext.EscapeForJS(true),
|
||||
jsontext.Multiline(true)) // expand for readability
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
|
||||
// Output:
|
||||
// {
|
||||
// "Title": "Example Embedded Javascript",
|
||||
// "Body": "\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e"
|
||||
// }
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"encoding/json/internal/jsontest"
|
||||
)
|
||||
|
||||
func FuzzCoder(f *testing.F) {
|
||||
// Add a number of inputs to the corpus including valid and invalid data.
|
||||
for _, td := range coderTestdata {
|
||||
f.Add(int64(0), []byte(td.in))
|
||||
}
|
||||
for _, td := range decoderErrorTestdata {
|
||||
f.Add(int64(0), []byte(td.in))
|
||||
}
|
||||
for _, td := range encoderErrorTestdata {
|
||||
f.Add(int64(0), []byte(td.wantOut))
|
||||
}
|
||||
for _, td := range jsontest.Data {
|
||||
f.Add(int64(0), td.Data())
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, seed int64, b []byte) {
|
||||
var tokVals []tokOrVal
|
||||
rn := rand.NewSource(seed)
|
||||
|
||||
// Read a sequence of tokens or values. Skip the test for any errors
|
||||
// since we expect this with randomly generated fuzz inputs.
|
||||
src := bytes.NewReader(b)
|
||||
dec := NewDecoder(src)
|
||||
for {
|
||||
if rn.Int63()%8 > 0 {
|
||||
tok, err := dec.ReadToken()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Skipf("Decoder.ReadToken error: %v", err)
|
||||
}
|
||||
tokVals = append(tokVals, tok.Clone())
|
||||
} else {
|
||||
val, err := dec.ReadValue()
|
||||
if err != nil {
|
||||
expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']'
|
||||
if expectError && errors.As(err, new(*SyntacticError)) {
|
||||
continue
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Skipf("Decoder.ReadValue error: %v", err)
|
||||
}
|
||||
tokVals = append(tokVals, append(zeroValue, val...))
|
||||
}
|
||||
}
|
||||
|
||||
// Write a sequence of tokens or values. Fail the test for any errors
|
||||
// since the previous stage guarantees that the input is valid.
|
||||
dst := new(bytes.Buffer)
|
||||
enc := NewEncoder(dst)
|
||||
for _, tokVal := range tokVals {
|
||||
switch tokVal := tokVal.(type) {
|
||||
case Token:
|
||||
if err := enc.WriteToken(tokVal); err != nil {
|
||||
t.Fatalf("Encoder.WriteToken error: %v", err)
|
||||
}
|
||||
case Value:
|
||||
if err := enc.WriteValue(tokVal); err != nil {
|
||||
t.Fatalf("Encoder.WriteValue error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encoded output and original input must decode to the same thing.
|
||||
var got, want []Token
|
||||
for dec := NewDecoder(bytes.NewReader(b)); dec.PeekKind() > 0; {
|
||||
tok, err := dec.ReadToken()
|
||||
if err != nil {
|
||||
t.Fatalf("Decoder.ReadToken error: %v", err)
|
||||
}
|
||||
got = append(got, tok.Clone())
|
||||
}
|
||||
for dec := NewDecoder(dst); dec.PeekKind() > 0; {
|
||||
tok, err := dec.ReadToken()
|
||||
if err != nil {
|
||||
t.Fatalf("Decoder.ReadToken error: %v", err)
|
||||
}
|
||||
want = append(want, tok.Clone())
|
||||
}
|
||||
if !equalTokens(got, want) {
|
||||
t.Fatalf("mismatching output:\ngot %v\nwant %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzResumableDecoder(f *testing.F) {
|
||||
for _, td := range resumableDecoderTestdata {
|
||||
f.Add(int64(0), []byte(td))
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, seed int64, b []byte) {
|
||||
rn := rand.NewSource(seed)
|
||||
|
||||
// Regardless of how many bytes the underlying io.Reader produces,
|
||||
// the provided tokens, values, and errors should always be identical.
|
||||
t.Run("ReadToken", func(t *testing.T) {
|
||||
decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn})
|
||||
decWant := NewDecoder(bytes.NewReader(b))
|
||||
gotTok, gotErr := decGot.ReadToken()
|
||||
wantTok, wantErr := decWant.ReadToken()
|
||||
if gotTok.String() != wantTok.String() || !equalError(gotErr, wantErr) {
|
||||
t.Errorf("Decoder.ReadToken = (%v, %v), want (%v, %v)", gotTok, gotErr, wantTok, wantErr)
|
||||
}
|
||||
})
|
||||
t.Run("ReadValue", func(t *testing.T) {
|
||||
decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn})
|
||||
decWant := NewDecoder(bytes.NewReader(b))
|
||||
gotVal, gotErr := decGot.ReadValue()
|
||||
wantVal, wantErr := decWant.ReadValue()
|
||||
if !slices.Equal(gotVal, wantVal) || !equalError(gotErr, wantErr) {
|
||||
t.Errorf("Decoder.ReadValue = (%s, %v), want (%s, %v)", gotVal, gotErr, wantVal, wantErr)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzValueFormat(f *testing.F) {
|
||||
for _, td := range valueTestdata {
|
||||
f.Add(int64(0), []byte(td.in))
|
||||
}
|
||||
|
||||
// isValid reports whether b is valid according to the specified options.
|
||||
isValid := func(b []byte, opts ...Options) bool {
|
||||
d := NewDecoder(bytes.NewReader(b), opts...)
|
||||
_, errVal := d.ReadValue()
|
||||
_, errEOF := d.ReadToken()
|
||||
return errVal == nil && errEOF == io.EOF
|
||||
}
|
||||
|
||||
// stripWhitespace removes all JSON whitespace characters from the input.
|
||||
stripWhitespace := func(in []byte) (out []byte) {
|
||||
out = make([]byte, 0, len(in))
|
||||
for _, c := range in {
|
||||
switch c {
|
||||
case ' ', '\n', '\r', '\t':
|
||||
default:
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
allOptions := []Options{
|
||||
AllowDuplicateNames(true),
|
||||
AllowInvalidUTF8(true),
|
||||
EscapeForHTML(true),
|
||||
EscapeForJS(true),
|
||||
PreserveRawStrings(true),
|
||||
CanonicalizeRawInts(true),
|
||||
CanonicalizeRawFloats(true),
|
||||
ReorderRawObjects(true),
|
||||
SpaceAfterColon(true),
|
||||
SpaceAfterComma(true),
|
||||
Multiline(true),
|
||||
WithIndent("\t"),
|
||||
WithIndentPrefix(" "),
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, seed int64, b []byte) {
|
||||
validRFC7159 := isValid(b, AllowInvalidUTF8(true), AllowDuplicateNames(true))
|
||||
validRFC8259 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(true))
|
||||
validRFC7493 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(false))
|
||||
switch {
|
||||
case !validRFC7159 && validRFC8259:
|
||||
t.Errorf("invalid input per RFC 7159 implies invalid per RFC 8259")
|
||||
case !validRFC8259 && validRFC7493:
|
||||
t.Errorf("invalid input per RFC 8259 implies invalid per RFC 7493")
|
||||
}
|
||||
|
||||
gotValid := Value(b).IsValid()
|
||||
wantValid := validRFC7493
|
||||
if gotValid != wantValid {
|
||||
t.Errorf("Value.IsValid = %v, want %v", gotValid, wantValid)
|
||||
}
|
||||
|
||||
gotCompacted := Value(string(b))
|
||||
gotCompactOk := gotCompacted.Compact() == nil
|
||||
wantCompactOk := validRFC7159
|
||||
if !bytes.Equal(stripWhitespace(gotCompacted), stripWhitespace(b)) {
|
||||
t.Errorf("stripWhitespace(Value.Compact) = %s, want %s", stripWhitespace(gotCompacted), stripWhitespace(b))
|
||||
}
|
||||
if gotCompactOk != wantCompactOk {
|
||||
t.Errorf("Value.Compact success mismatch: got %v, want %v", gotCompactOk, wantCompactOk)
|
||||
}
|
||||
|
||||
gotIndented := Value(string(b))
|
||||
gotIndentOk := gotIndented.Indent() == nil
|
||||
wantIndentOk := validRFC7159
|
||||
if !bytes.Equal(stripWhitespace(gotIndented), stripWhitespace(b)) {
|
||||
t.Errorf("stripWhitespace(Value.Indent) = %s, want %s", stripWhitespace(gotIndented), stripWhitespace(b))
|
||||
}
|
||||
if gotIndentOk != wantIndentOk {
|
||||
t.Errorf("Value.Indent success mismatch: got %v, want %v", gotIndentOk, wantIndentOk)
|
||||
}
|
||||
|
||||
gotCanonicalized := Value(string(b))
|
||||
gotCanonicalizeOk := gotCanonicalized.Canonicalize() == nil
|
||||
wantCanonicalizeOk := validRFC7493
|
||||
if gotCanonicalizeOk != wantCanonicalizeOk {
|
||||
t.Errorf("Value.Canonicalize success mismatch: got %v, want %v", gotCanonicalizeOk, wantCanonicalizeOk)
|
||||
}
|
||||
|
||||
// Random options should not result in a panic.
|
||||
var opts []Options
|
||||
rn := rand.New(rand.NewSource(seed))
|
||||
for _, opt := range allOptions {
|
||||
if rn.Intn(len(allOptions)/4) == 0 {
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
}
|
||||
v := Value(b)
|
||||
v.Format(opts...) // should not panic
|
||||
})
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func TestPointer(t *testing.T) {
|
||||
tests := []struct {
|
||||
in Pointer
|
||||
wantParent Pointer
|
||||
wantLast string
|
||||
wantTokens []string
|
||||
wantValid bool
|
||||
}{
|
||||
{"", "", "", nil, true},
|
||||
{"a", "", "a", []string{"a"}, false},
|
||||
{"~", "", "~", []string{"~"}, false},
|
||||
{"/a", "", "a", []string{"a"}, true},
|
||||
{"/foo/bar", "/foo", "bar", []string{"foo", "bar"}, true},
|
||||
{"///", "//", "", []string{"", "", ""}, true},
|
||||
{"/~0~1", "", "~/", []string{"~/"}, true},
|
||||
{"/\xde\xad\xbe\xef", "", "\xde\xad\xbe\xef", []string{"\xde\xad\xbe\xef"}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := tt.in.Parent(); got != tt.wantParent {
|
||||
t.Errorf("Pointer(%q).Parent = %q, want %q", tt.in, got, tt.wantParent)
|
||||
}
|
||||
if got := tt.in.LastToken(); got != tt.wantLast {
|
||||
t.Errorf("Pointer(%q).Last = %q, want %q", tt.in, got, tt.wantLast)
|
||||
}
|
||||
if strings.HasPrefix(string(tt.in), "/") {
|
||||
wantRoundtrip := tt.in
|
||||
if !utf8.ValidString(string(wantRoundtrip)) {
|
||||
// Replace bytes of invalid UTF-8 with Unicode replacement character.
|
||||
wantRoundtrip = Pointer([]rune(wantRoundtrip))
|
||||
}
|
||||
if got := tt.in.Parent().AppendToken(tt.in.LastToken()); got != wantRoundtrip {
|
||||
t.Errorf("Pointer(%q).Parent().AppendToken(LastToken()) = %q, want %q", tt.in, got, tt.in)
|
||||
}
|
||||
in := tt.in
|
||||
for {
|
||||
if (in + "x").Contains(tt.in) {
|
||||
t.Errorf("Pointer(%q).Contains(%q) = true, want false", in+"x", tt.in)
|
||||
}
|
||||
if !in.Contains(tt.in) {
|
||||
t.Errorf("Pointer(%q).Contains(%q) = false, want true", in, tt.in)
|
||||
}
|
||||
if in == in.Parent() {
|
||||
break
|
||||
}
|
||||
in = in.Parent()
|
||||
}
|
||||
}
|
||||
if got := slices.Collect(tt.in.Tokens()); !slices.Equal(got, tt.wantTokens) {
|
||||
t.Errorf("Pointer(%q).Tokens = %q, want %q", tt.in, got, tt.wantTokens)
|
||||
}
|
||||
if got := tt.in.IsValid(); got != tt.wantValid {
|
||||
t.Errorf("Pointer(%q).IsValid = %v, want %v", tt.in, got, tt.wantValid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateMachine(t *testing.T) {
|
||||
// To test a state machine, we pass an ordered sequence of operations and
|
||||
// check whether the current state is as expected.
|
||||
// The operation type is a union type of various possible operations,
|
||||
// which either call mutating methods on the state machine or
|
||||
// call accessor methods on state machine and verify the results.
|
||||
type operation any
|
||||
type (
|
||||
// stackLengths checks the results of stateEntry.length accessors.
|
||||
stackLengths []int64
|
||||
|
||||
// appendTokens is sequence of token kinds to append where
|
||||
// none of them are expected to fail.
|
||||
//
|
||||
// For example: `[nft]` is equivalent to the following sequence:
|
||||
//
|
||||
// pushArray()
|
||||
// appendLiteral()
|
||||
// appendString()
|
||||
// appendNumber()
|
||||
// popArray()
|
||||
//
|
||||
appendTokens string
|
||||
|
||||
// appendToken is a single token kind to append with the expected error.
|
||||
appendToken struct {
|
||||
kind Kind
|
||||
want error
|
||||
}
|
||||
|
||||
// needDelim checks the result of the needDelim accessor.
|
||||
needDelim struct {
|
||||
next Kind
|
||||
want byte
|
||||
}
|
||||
)
|
||||
|
||||
// Each entry is a sequence of tokens to pass to the state machine.
|
||||
tests := []struct {
|
||||
label string
|
||||
ops []operation
|
||||
}{{
|
||||
"TopLevelValues",
|
||||
[]operation{
|
||||
stackLengths{0},
|
||||
needDelim{'n', 0},
|
||||
appendTokens(`nft`),
|
||||
stackLengths{3},
|
||||
needDelim{'"', 0},
|
||||
appendTokens(`"0[]{}`),
|
||||
stackLengths{7},
|
||||
},
|
||||
}, {
|
||||
"ArrayValues",
|
||||
[]operation{
|
||||
stackLengths{0},
|
||||
needDelim{'[', 0},
|
||||
appendTokens(`[`),
|
||||
stackLengths{1, 0},
|
||||
needDelim{'n', 0},
|
||||
appendTokens(`nft`),
|
||||
stackLengths{1, 3},
|
||||
needDelim{'"', ','},
|
||||
appendTokens(`"0[]{}`),
|
||||
stackLengths{1, 7},
|
||||
needDelim{']', 0},
|
||||
appendTokens(`]`),
|
||||
stackLengths{1},
|
||||
},
|
||||
}, {
|
||||
"ObjectValues",
|
||||
[]operation{
|
||||
stackLengths{0},
|
||||
needDelim{'{', 0},
|
||||
appendTokens(`{`),
|
||||
stackLengths{1, 0},
|
||||
needDelim{'"', 0},
|
||||
appendTokens(`"`),
|
||||
stackLengths{1, 1},
|
||||
needDelim{'n', ':'},
|
||||
appendTokens(`n`),
|
||||
stackLengths{1, 2},
|
||||
needDelim{'"', ','},
|
||||
appendTokens(`"f"t`),
|
||||
stackLengths{1, 6},
|
||||
appendTokens(`"""0"[]"{}`),
|
||||
stackLengths{1, 14},
|
||||
needDelim{'}', 0},
|
||||
appendTokens(`}`),
|
||||
stackLengths{1},
|
||||
},
|
||||
}, {
|
||||
"ObjectCardinality",
|
||||
[]operation{
|
||||
appendTokens(`{`),
|
||||
|
||||
// Appending any kind other than string for object name is an error.
|
||||
appendToken{'n', ErrNonStringName},
|
||||
appendToken{'f', ErrNonStringName},
|
||||
appendToken{'t', ErrNonStringName},
|
||||
appendToken{'0', ErrNonStringName},
|
||||
appendToken{'{', ErrNonStringName},
|
||||
appendToken{'[', ErrNonStringName},
|
||||
appendTokens(`"`),
|
||||
|
||||
// Appending '}' without first appending any value is an error.
|
||||
appendToken{'}', errMissingValue},
|
||||
appendTokens(`"`),
|
||||
|
||||
appendTokens(`}`),
|
||||
},
|
||||
}, {
|
||||
"MismatchingDelims",
|
||||
[]operation{
|
||||
appendToken{'}', errMismatchDelim}, // appending '}' without preceding '{'
|
||||
appendTokens(`[[{`),
|
||||
appendToken{']', errMismatchDelim}, // appending ']' that mismatches preceding '{'
|
||||
appendTokens(`}]`),
|
||||
appendToken{'}', errMismatchDelim}, // appending '}' that mismatches preceding '['
|
||||
appendTokens(`]`),
|
||||
appendToken{']', errMismatchDelim}, // appending ']' without preceding '['
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.label, func(t *testing.T) {
|
||||
// Flatten appendTokens to sequence of appendToken entries.
|
||||
var ops []operation
|
||||
for _, op := range tt.ops {
|
||||
if toks, ok := op.(appendTokens); ok {
|
||||
for _, k := range []byte(toks) {
|
||||
ops = append(ops, appendToken{Kind(k), nil})
|
||||
}
|
||||
continue
|
||||
}
|
||||
ops = append(ops, op)
|
||||
}
|
||||
|
||||
// Append each token to the state machine and check the output.
|
||||
var state stateMachine
|
||||
state.reset()
|
||||
var sequence []Kind
|
||||
for _, op := range ops {
|
||||
switch op := op.(type) {
|
||||
case stackLengths:
|
||||
var got []int64
|
||||
for i := range state.Depth() {
|
||||
e := state.index(i)
|
||||
got = append(got, e.Length())
|
||||
}
|
||||
want := []int64(op)
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("%s: stack lengths mismatch:\ngot %v\nwant %v", sequence, got, want)
|
||||
}
|
||||
case appendToken:
|
||||
got := state.append(op.kind)
|
||||
if !equalError(got, op.want) {
|
||||
t.Fatalf("%s: append('%c') = %v, want %v", sequence, op.kind, got, op.want)
|
||||
}
|
||||
if got == nil {
|
||||
sequence = append(sequence, op.kind)
|
||||
}
|
||||
case needDelim:
|
||||
if got := state.needDelim(op.next); got != op.want {
|
||||
t.Fatalf("%s: needDelim('%c') = '%c', want '%c'", sequence, op.next, got, op.want)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown operation: %T", op))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// append is a thin wrapper over the other append, pop, or push methods
|
||||
// based on the token kind.
|
||||
func (s *stateMachine) append(k Kind) error {
|
||||
switch k {
|
||||
case 'n', 'f', 't':
|
||||
return s.appendLiteral()
|
||||
case '"':
|
||||
return s.appendString()
|
||||
case '0':
|
||||
return s.appendNumber()
|
||||
case '{':
|
||||
return s.pushObject()
|
||||
case '}':
|
||||
return s.popObject()
|
||||
case '[':
|
||||
return s.pushArray()
|
||||
case ']':
|
||||
return s.popArray()
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid token kind: '%c'", k))
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectNamespace(t *testing.T) {
|
||||
type operation any
|
||||
type (
|
||||
insert struct {
|
||||
name string
|
||||
wantInserted bool
|
||||
}
|
||||
removeLast struct{}
|
||||
)
|
||||
|
||||
// Sequence of insert operations to perform (order matters).
|
||||
ops := []operation{
|
||||
insert{`""`, true},
|
||||
removeLast{},
|
||||
insert{`""`, true},
|
||||
insert{`""`, false},
|
||||
|
||||
// Test insertion of the same name with different formatting.
|
||||
insert{`"alpha"`, true},
|
||||
insert{`"ALPHA"`, true}, // case-sensitive matching
|
||||
insert{`"alpha"`, false},
|
||||
insert{`"\u0061\u006c\u0070\u0068\u0061"`, false}, // unescapes to "alpha"
|
||||
removeLast{}, // removes "ALPHA"
|
||||
insert{`"alpha"`, false},
|
||||
removeLast{}, // removes "alpha"
|
||||
insert{`"alpha"`, true},
|
||||
removeLast{},
|
||||
|
||||
// Bulk insert simple names.
|
||||
insert{`"alpha"`, true},
|
||||
insert{`"bravo"`, true},
|
||||
insert{`"charlie"`, true},
|
||||
insert{`"delta"`, true},
|
||||
insert{`"echo"`, true},
|
||||
insert{`"foxtrot"`, true},
|
||||
insert{`"golf"`, true},
|
||||
insert{`"hotel"`, true},
|
||||
insert{`"india"`, true},
|
||||
insert{`"juliet"`, true},
|
||||
insert{`"kilo"`, true},
|
||||
insert{`"lima"`, true},
|
||||
insert{`"mike"`, true},
|
||||
insert{`"november"`, true},
|
||||
insert{`"oscar"`, true},
|
||||
insert{`"papa"`, true},
|
||||
insert{`"quebec"`, true},
|
||||
insert{`"romeo"`, true},
|
||||
insert{`"sierra"`, true},
|
||||
insert{`"tango"`, true},
|
||||
insert{`"uniform"`, true},
|
||||
insert{`"victor"`, true},
|
||||
insert{`"whiskey"`, true},
|
||||
insert{`"xray"`, true},
|
||||
insert{`"yankee"`, true},
|
||||
insert{`"zulu"`, true},
|
||||
|
||||
// Test insertion of invalid UTF-8.
|
||||
insert{`"` + "\ufffd" + `"`, true},
|
||||
insert{`"` + "\ufffd" + `"`, false},
|
||||
insert{`"\ufffd"`, false}, // unescapes to Unicode replacement character
|
||||
insert{`"\uFFFD"`, false}, // unescapes to Unicode replacement character
|
||||
insert{`"` + "\xff" + `"`, false}, // mangles as Unicode replacement character
|
||||
removeLast{},
|
||||
insert{`"` + "\ufffd" + `"`, true},
|
||||
|
||||
// Test insertion of unicode characters.
|
||||
insert{`"☺☻☹"`, true},
|
||||
insert{`"☺☻☹"`, false},
|
||||
removeLast{},
|
||||
insert{`"☺☻☹"`, true},
|
||||
}
|
||||
|
||||
// Execute the sequence of operations twice:
|
||||
// 1) on a fresh namespace and 2) on a namespace that has been reset.
|
||||
var ns objectNamespace
|
||||
wantNames := []string{}
|
||||
for _, reset := range []bool{false, true} {
|
||||
if reset {
|
||||
ns.reset()
|
||||
wantNames = nil
|
||||
}
|
||||
|
||||
// Execute the operations and ensure the state is consistent.
|
||||
for i, op := range ops {
|
||||
switch op := op.(type) {
|
||||
case insert:
|
||||
gotInserted := ns.insertQuoted([]byte(op.name), false)
|
||||
if gotInserted != op.wantInserted {
|
||||
t.Fatalf("%d: objectNamespace{%v}.insert(%v) = %v, want %v", i, strings.Join(wantNames, " "), op.name, gotInserted, op.wantInserted)
|
||||
}
|
||||
if gotInserted {
|
||||
b, _ := AppendUnquote(nil, []byte(op.name))
|
||||
wantNames = append(wantNames, string(b))
|
||||
}
|
||||
case removeLast:
|
||||
ns.removeLast()
|
||||
wantNames = wantNames[:len(wantNames)-1]
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown operation: %T", op))
|
||||
}
|
||||
|
||||
// Check that the namespace is consistent.
|
||||
gotNames := []string{}
|
||||
for i := range ns.length() {
|
||||
gotNames = append(gotNames, string(ns.getUnquoted(i)))
|
||||
}
|
||||
if !slices.Equal(gotNames, wantNames) {
|
||||
t.Fatalf("%d: objectNamespace = {%v}, want {%v}", i, strings.Join(gotNames, " "), strings.Join(wantNames, " "))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we have not switched to using a Go map.
|
||||
if ns.mapNames != nil {
|
||||
t.Errorf("objectNamespace.mapNames = non-nil, want nil")
|
||||
}
|
||||
|
||||
// Insert a large number of names.
|
||||
for i := range 64 {
|
||||
ns.InsertUnquoted([]byte(fmt.Sprintf(`name%d`, i)))
|
||||
}
|
||||
|
||||
// Verify that we did switch to using a Go map.
|
||||
if ns.mapNames == nil {
|
||||
t.Errorf("objectNamespace.mapNames = nil, want non-nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package jsontext
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTokenStringAllocations(t *testing.T) {
|
||||
if testing.CoverMode() != "" {
|
||||
t.Skip("coverage mode breaks the compiler optimization this depends on")
|
||||
}
|
||||
|
||||
tok := rawToken(`"hello"`)
|
||||
var m map[string]bool
|
||||
got := int(testing.AllocsPerRun(10, func() {
|
||||
// This function uses tok.String() is a non-escaping manner
|
||||
// (i.e., looking it up in a Go map). It should not allocate.
|
||||
if m[tok.String()] {
|
||||
panic("never executed")
|
||||
}
|
||||
}))
|
||||
if got > 0 {
|
||||
t.Errorf("Token.String allocated %d times, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenAccessors(t *testing.T) {
|
||||
type token struct {
|
||||
Bool bool
|
||||
String string
|
||||
Float float64
|
||||
Int int64
|
||||
Uint uint64
|
||||
Kind Kind
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in Token
|
||||
want token
|
||||
}{
|
||||
{Token{}, token{String: "<invalid jsontext.Token>"}},
|
||||
{Null, token{String: "null", Kind: 'n'}},
|
||||
{False, token{Bool: false, String: "false", Kind: 'f'}},
|
||||
{True, token{Bool: true, String: "true", Kind: 't'}},
|
||||
{Bool(false), token{Bool: false, String: "false", Kind: 'f'}},
|
||||
{Bool(true), token{Bool: true, String: "true", Kind: 't'}},
|
||||
{BeginObject, token{String: "{", Kind: '{'}},
|
||||
{EndObject, token{String: "}", Kind: '}'}},
|
||||
{BeginArray, token{String: "[", Kind: '['}},
|
||||
{EndArray, token{String: "]", Kind: ']'}},
|
||||
{String(""), token{String: "", Kind: '"'}},
|
||||
{String("hello, world!"), token{String: "hello, world!", Kind: '"'}},
|
||||
{rawToken(`"hello, world!"`), token{String: "hello, world!", Kind: '"'}},
|
||||
{Float(0), token{String: "0", Float: 0, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float(math.Copysign(0, -1)), token{String: "-0", Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float(math.NaN()), token{String: "NaN", Float: math.NaN(), Int: 0, Uint: 0, Kind: '"'}},
|
||||
{Float(math.Inf(+1)), token{String: "Infinity", Float: math.Inf(+1), Kind: '"'}},
|
||||
{Float(math.Inf(-1)), token{String: "-Infinity", Float: math.Inf(-1), Kind: '"'}},
|
||||
{Int(minInt64), token{String: "-9223372036854775808", Float: minInt64, Int: minInt64, Uint: minUint64, Kind: '0'}},
|
||||
{Int(minInt64 + 1), token{String: "-9223372036854775807", Float: minInt64 + 1, Int: minInt64 + 1, Uint: minUint64, Kind: '0'}},
|
||||
{Int(-1), token{String: "-1", Float: -1, Int: -1, Uint: minUint64, Kind: '0'}},
|
||||
{Int(0), token{String: "0", Float: 0, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Int(+1), token{String: "1", Float: +1, Int: +1, Uint: +1, Kind: '0'}},
|
||||
{Int(maxInt64 - 1), token{String: "9223372036854775806", Float: maxInt64 - 1, Int: maxInt64 - 1, Uint: maxInt64 - 1, Kind: '0'}},
|
||||
{Int(maxInt64), token{String: "9223372036854775807", Float: maxInt64, Int: maxInt64, Uint: maxInt64, Kind: '0'}},
|
||||
{Uint(minUint64), token{String: "0", Kind: '0'}},
|
||||
{Uint(minUint64 + 1), token{String: "1", Float: minUint64 + 1, Int: minUint64 + 1, Uint: minUint64 + 1, Kind: '0'}},
|
||||
{Uint(maxUint64 - 1), token{String: "18446744073709551614", Float: maxUint64 - 1, Int: maxInt64, Uint: maxUint64 - 1, Kind: '0'}},
|
||||
{Uint(maxUint64), token{String: "18446744073709551615", Float: maxUint64, Int: maxInt64, Uint: maxUint64, Kind: '0'}},
|
||||
{rawToken(`-0`), token{String: "-0", Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`1e1000`), token{String: "1e1000", Float: math.MaxFloat64, Int: maxInt64, Uint: maxUint64, Kind: '0'}},
|
||||
{rawToken(`-1e1000`), token{String: "-1e1000", Float: -math.MaxFloat64, Int: minInt64, Uint: minUint64, Kind: '0'}},
|
||||
{rawToken(`0.1`), token{String: "0.1", Float: 0.1, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`0.5`), token{String: "0.5", Float: 0.5, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`0.9`), token{String: "0.9", Float: 0.9, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`1.1`), token{String: "1.1", Float: 1.1, Int: 1, Uint: 1, Kind: '0'}},
|
||||
{rawToken(`-0.1`), token{String: "-0.1", Float: -0.1, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`-0.5`), token{String: "-0.5", Float: -0.5, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`-0.9`), token{String: "-0.9", Float: -0.9, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`-1.1`), token{String: "-1.1", Float: -1.1, Int: -1, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`99999999999999999999`), token{String: "99999999999999999999", Float: 1e20 - 1, Int: maxInt64, Uint: maxUint64, Kind: '0'}},
|
||||
{rawToken(`-99999999999999999999`), token{String: "-99999999999999999999", Float: -1e20 - 1, Int: minInt64, Uint: minUint64, Kind: '0'}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got := token{
|
||||
Bool: func() bool {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Bool()
|
||||
}(),
|
||||
String: tt.in.String(),
|
||||
Float: func() float64 {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Float()
|
||||
}(),
|
||||
Int: func() int64 {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Int()
|
||||
}(),
|
||||
Uint: func() uint64 {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Uint()
|
||||
}(),
|
||||
Kind: tt.in.Kind(),
|
||||
}
|
||||
|
||||
if got.Bool != tt.want.Bool {
|
||||
t.Errorf("Token(%s).Bool() = %v, want %v", tt.in, got.Bool, tt.want.Bool)
|
||||
}
|
||||
if got.String != tt.want.String {
|
||||
t.Errorf("Token(%s).String() = %v, want %v", tt.in, got.String, tt.want.String)
|
||||
}
|
||||
if math.Float64bits(got.Float) != math.Float64bits(tt.want.Float) {
|
||||
t.Errorf("Token(%s).Float() = %v, want %v", tt.in, got.Float, tt.want.Float)
|
||||
}
|
||||
if got.Int != tt.want.Int {
|
||||
t.Errorf("Token(%s).Int() = %v, want %v", tt.in, got.Int, tt.want.Int)
|
||||
}
|
||||
if got.Uint != tt.want.Uint {
|
||||
t.Errorf("Token(%s).Uint() = %v, want %v", tt.in, got.Uint, tt.want.Uint)
|
||||
}
|
||||
if got.Kind != tt.want.Kind {
|
||||
t.Errorf("Token(%s).Kind() = %v, want %v", tt.in, got.Kind, tt.want.Kind)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenClone(t *testing.T) {
|
||||
tests := []struct {
|
||||
in Token
|
||||
wantExactRaw bool
|
||||
}{
|
||||
{Token{}, true},
|
||||
{Null, true},
|
||||
{False, true},
|
||||
{True, true},
|
||||
{BeginObject, true},
|
||||
{EndObject, true},
|
||||
{BeginArray, true},
|
||||
{EndArray, true},
|
||||
{String("hello, world!"), true},
|
||||
{rawToken(`"hello, world!"`), false},
|
||||
{Float(3.14159), true},
|
||||
{rawToken(`3.14159`), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got := tt.in.Clone()
|
||||
if !reflect.DeepEqual(got, tt.in) {
|
||||
t.Errorf("Token(%s) == Token(%s).Clone() = false, want true", tt.in, tt.in)
|
||||
}
|
||||
gotExactRaw := got.raw == tt.in.raw
|
||||
if gotExactRaw != tt.wantExactRaw {
|
||||
t.Errorf("Token(%s).raw == Token(%s).Clone().raw = %v, want %v", tt.in, tt.in, gotExactRaw, tt.wantExactRaw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNumberIsValid(t *testing.T) {
|
||||
// From: https://stackoverflow.com/a/13340826
|
||||
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
|
||||
|
||||
validTests := []string{
|
||||
"0",
|
||||
"-0",
|
||||
"1",
|
||||
"-1",
|
||||
"0.1",
|
||||
"-0.1",
|
||||
"1234",
|
||||
"-1234",
|
||||
"12.34",
|
||||
"-12.34",
|
||||
"12E0",
|
||||
"12E1",
|
||||
"12e34",
|
||||
"12E-0",
|
||||
"12e+1",
|
||||
"12e-34",
|
||||
"-12E0",
|
||||
"-12E1",
|
||||
"-12e34",
|
||||
"-12E-0",
|
||||
"-12e+1",
|
||||
"-12e-34",
|
||||
"1.2E0",
|
||||
"1.2E1",
|
||||
"1.2e34",
|
||||
"1.2E-0",
|
||||
"1.2e+1",
|
||||
"1.2e-34",
|
||||
"-1.2E0",
|
||||
"-1.2E1",
|
||||
"-1.2e34",
|
||||
"-1.2E-0",
|
||||
"-1.2e+1",
|
||||
"-1.2e-34",
|
||||
"0E0",
|
||||
"0E1",
|
||||
"0e34",
|
||||
"0E-0",
|
||||
"0e+1",
|
||||
"0e-34",
|
||||
"-0E0",
|
||||
"-0E1",
|
||||
"-0e34",
|
||||
"-0E-0",
|
||||
"-0e+1",
|
||||
"-0e-34",
|
||||
}
|
||||
|
||||
for _, test := range validTests {
|
||||
if !isValidNumber(test) {
|
||||
t.Errorf("%s should be valid", test)
|
||||
}
|
||||
|
||||
var f float64
|
||||
if err := Unmarshal([]byte(test), &f); err != nil {
|
||||
t.Errorf("%s should be valid but Unmarshal failed: %v", test, err)
|
||||
}
|
||||
|
||||
if !jsonNumberRegexp.MatchString(test) {
|
||||
t.Errorf("%s should be valid but regexp does not match", test)
|
||||
}
|
||||
}
|
||||
|
||||
invalidTests := []string{
|
||||
"",
|
||||
"invalid",
|
||||
"1.0.1",
|
||||
"1..1",
|
||||
"-1-2",
|
||||
"012a42",
|
||||
"01.2",
|
||||
"012",
|
||||
"12E12.12",
|
||||
"1e2e3",
|
||||
"1e+-2",
|
||||
"1e--23",
|
||||
"1e",
|
||||
"e1",
|
||||
"1e+",
|
||||
"1ea",
|
||||
"1a",
|
||||
"1.a",
|
||||
"1.",
|
||||
"01",
|
||||
"1.e1",
|
||||
}
|
||||
|
||||
for _, test := range invalidTests {
|
||||
if isValidNumber(test) {
|
||||
t.Errorf("%s should be invalid", test)
|
||||
}
|
||||
|
||||
var f float64
|
||||
if err := Unmarshal([]byte(test), &f); err == nil {
|
||||
t.Errorf("%s should be invalid but unmarshal wrote %v", test, f)
|
||||
}
|
||||
|
||||
if jsonNumberRegexp.MatchString(test) {
|
||||
t.Errorf("%s should be invalid but matches regexp", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func indentNewlines(s string) string {
|
||||
return strings.Join(strings.Split(s, "\n"), "\n\t")
|
||||
}
|
||||
|
||||
func stripWhitespace(s string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r == ' ' || r == '\n' || r == '\r' || r == '\t' {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, s)
|
||||
}
|
||||
|
||||
func TestValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
data string
|
||||
ok bool
|
||||
}{
|
||||
{Name(""), `foo`, false},
|
||||
{Name(""), `}{`, false},
|
||||
{Name(""), `{]`, false},
|
||||
{Name(""), `{}`, true},
|
||||
{Name(""), `{"foo":"bar"}`, true},
|
||||
{Name(""), `{"foo":"bar","bar":{"baz":["qux"]}}`, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
if ok := Valid([]byte(tt.data)); ok != tt.ok {
|
||||
t.Errorf("%s: Valid(`%s`) = %v, want %v", tt.Where, tt.data, ok, tt.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactAndIndent(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
compact string
|
||||
indent string
|
||||
}{
|
||||
{Name(""), `1`, `1`},
|
||||
{Name(""), `{}`, `{}`},
|
||||
{Name(""), `[]`, `[]`},
|
||||
{Name(""), `{"":2}`, "{\n\t\"\": 2\n}"},
|
||||
{Name(""), `[3]`, "[\n\t3\n]"},
|
||||
{Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
|
||||
{Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"},
|
||||
{Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
"x",
|
||||
1,
|
||||
1.5,
|
||||
0,
|
||||
-5e+2
|
||||
]`},
|
||||
{Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
buf.Reset()
|
||||
if err := Compact(&buf, []byte(tt.compact)); err != nil {
|
||||
t.Errorf("%s: Compact error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.compact {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := Compact(&buf, []byte(tt.indent)); err != nil {
|
||||
t.Errorf("%s: Compact error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.compact {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil {
|
||||
t.Errorf("%s: Indent error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.indent {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil {
|
||||
t.Errorf("%s: Indent error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.indent {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactSeparators(t *testing.T) {
|
||||
// U+2028 and U+2029 should be escaped inside strings.
|
||||
// They should not appear outside strings.
|
||||
tests := []struct {
|
||||
CaseName
|
||||
in, compact string
|
||||
}{
|
||||
{Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"},
|
||||
{Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := Compact(&buf, []byte(tt.in)); err != nil {
|
||||
t.Errorf("%s: Compact error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.compact {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of a large random structure.
|
||||
|
||||
func TestCompactBig(t *testing.T) {
|
||||
initBig()
|
||||
var buf bytes.Buffer
|
||||
if err := Compact(&buf, jsonBig); err != nil {
|
||||
t.Fatalf("Compact error: %v", err)
|
||||
}
|
||||
b := buf.Bytes()
|
||||
if !bytes.Equal(b, jsonBig) {
|
||||
t.Error("Compact:")
|
||||
diff(t, b, jsonBig)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndentBig(t *testing.T) {
|
||||
t.Parallel()
|
||||
initBig()
|
||||
var buf bytes.Buffer
|
||||
if err := Indent(&buf, jsonBig, "", "\t"); err != nil {
|
||||
t.Fatalf("Indent error: %v", err)
|
||||
}
|
||||
b := buf.Bytes()
|
||||
if len(b) == len(jsonBig) {
|
||||
// jsonBig is compact (no unnecessary spaces);
|
||||
// indenting should make it bigger
|
||||
t.Fatalf("Indent did not expand the input")
|
||||
}
|
||||
|
||||
// should be idempotent
|
||||
var buf1 bytes.Buffer
|
||||
if err := Indent(&buf1, b, "", "\t"); err != nil {
|
||||
t.Fatalf("Indent error: %v", err)
|
||||
}
|
||||
b1 := buf1.Bytes()
|
||||
if !bytes.Equal(b1, b) {
|
||||
t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):")
|
||||
diff(t, b1, b)
|
||||
return
|
||||
}
|
||||
|
||||
// should get back to original
|
||||
buf1.Reset()
|
||||
if err := Compact(&buf1, b); err != nil {
|
||||
t.Fatalf("Compact error: %v", err)
|
||||
}
|
||||
b1 = buf1.Bytes()
|
||||
if !bytes.Equal(b1, jsonBig) {
|
||||
t.Error("Compact(Indent(jsonBig)) != jsonBig:")
|
||||
diff(t, b1, jsonBig)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndentErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
in string
|
||||
err error
|
||||
}{
|
||||
{Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}},
|
||||
{Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
slice := make([]uint8, 0)
|
||||
buf := bytes.NewBuffer(slice)
|
||||
if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {
|
||||
if !reflect.DeepEqual(err, tt.err) {
|
||||
t.Fatalf("%s: Indent error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func diff(t *testing.T, a, b []byte) {
|
||||
t.Helper()
|
||||
for i := 0; ; i++ {
|
||||
if i >= len(a) || i >= len(b) || a[i] != b[i] {
|
||||
j := i - 10
|
||||
if j < 0 {
|
||||
j = 0
|
||||
}
|
||||
t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:]))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func trim(b []byte) []byte {
|
||||
return b[:min(len(b), 20)]
|
||||
}
|
||||
|
||||
// Generate a random JSON object.
|
||||
|
||||
var jsonBig []byte
|
||||
|
||||
func initBig() {
|
||||
n := 10000
|
||||
if testing.Short() {
|
||||
n = 100
|
||||
}
|
||||
b, err := Marshal(genValue(n))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
jsonBig = b
|
||||
}
|
||||
|
||||
func genValue(n int) any {
|
||||
if n > 1 {
|
||||
switch rand.Intn(2) {
|
||||
case 0:
|
||||
return genArray(n)
|
||||
case 1:
|
||||
return genMap(n)
|
||||
}
|
||||
}
|
||||
switch rand.Intn(3) {
|
||||
case 0:
|
||||
return rand.Intn(2) == 0
|
||||
case 1:
|
||||
return rand.NormFloat64()
|
||||
case 2:
|
||||
return genString(30)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func genString(stddev float64) string {
|
||||
n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2))
|
||||
c := make([]rune, n)
|
||||
for i := range c {
|
||||
f := math.Abs(rand.NormFloat64()*64 + 32)
|
||||
if f > 0x10ffff {
|
||||
f = 0x10ffff
|
||||
}
|
||||
c[i] = rune(f)
|
||||
}
|
||||
return string(c)
|
||||
}
|
||||
|
||||
func genArray(n int) []any {
|
||||
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
|
||||
if f > n {
|
||||
f = n
|
||||
}
|
||||
if f < 1 {
|
||||
f = 1
|
||||
}
|
||||
x := make([]any, f)
|
||||
for i := range x {
|
||||
x[i] = genValue(((i+1)*n)/f - (i*n)/f)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func genMap(n int) map[string]any {
|
||||
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
|
||||
if f > n {
|
||||
f = n
|
||||
}
|
||||
if n > 0 && f == 0 {
|
||||
f = 1
|
||||
}
|
||||
x := make(map[string]any)
|
||||
for i := 0; i < f; i++ {
|
||||
x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f)
|
||||
}
|
||||
return x
|
||||
}
|
||||
@@ -1,524 +0,0 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TODO(https://go.dev/issue/52751): Replace with native testing support.
|
||||
|
||||
// CaseName is a case name annotated with a file and line.
|
||||
type CaseName struct {
|
||||
Name string
|
||||
Where CasePos
|
||||
}
|
||||
|
||||
// Name annotates a case name with the file and line of the caller.
|
||||
func Name(s string) (c CaseName) {
|
||||
c.Name = s
|
||||
runtime.Callers(2, c.Where.pc[:])
|
||||
return c
|
||||
}
|
||||
|
||||
// CasePos represents a file and line number.
|
||||
type CasePos struct{ pc [1]uintptr }
|
||||
|
||||
func (pos CasePos) String() string {
|
||||
frames := runtime.CallersFrames(pos.pc[:])
|
||||
frame, _ := frames.Next()
|
||||
return fmt.Sprintf("%s:%d", path.Base(frame.File), frame.Line)
|
||||
}
|
||||
|
||||
// Test values for the stream test.
|
||||
// One of each JSON kind.
|
||||
var streamTest = []any{
|
||||
0.1,
|
||||
"hello",
|
||||
nil,
|
||||
true,
|
||||
false,
|
||||
[]any{"a", "b", "c"},
|
||||
map[string]any{"K": "Kelvin", "ß": "long s"},
|
||||
3.14, // another value to make sure something can follow map
|
||||
}
|
||||
|
||||
var streamEncoded = `0.1
|
||||
"hello"
|
||||
null
|
||||
true
|
||||
false
|
||||
["a","b","c"]
|
||||
{"ß":"long s","K":"Kelvin"}
|
||||
3.14
|
||||
`
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
for i := 0; i <= len(streamTest); i++ {
|
||||
var buf strings.Builder
|
||||
enc := NewEncoder(&buf)
|
||||
// Check that enc.SetIndent("", "") turns off indentation.
|
||||
enc.SetIndent(">", ".")
|
||||
enc.SetIndent("", "")
|
||||
for j, v := range streamTest[0:i] {
|
||||
if err := enc.Encode(v); err != nil {
|
||||
t.Fatalf("#%d.%d Encode error: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
if got, want := buf.String(), nlines(streamEncoded, i); got != want {
|
||||
t.Errorf("encoding %d items: mismatch:", i)
|
||||
diff(t, []byte(got), []byte(want))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderErrorAndReuseEncodeState(t *testing.T) {
|
||||
// Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test.
|
||||
percent := debug.SetGCPercent(-1)
|
||||
defer debug.SetGCPercent(percent)
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
if err := enc.Encode(dummy); err == nil {
|
||||
t.Errorf("Encode(dummy) error: got nil, want non-nil")
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
A string
|
||||
I int
|
||||
}
|
||||
want := Data{A: "a", I: 1}
|
||||
if err := enc.Encode(want); err != nil {
|
||||
t.Errorf("Marshal error: %v", err)
|
||||
}
|
||||
|
||||
var got Data
|
||||
if err := Unmarshal(buf.Bytes(), &got); err != nil {
|
||||
t.Errorf("Unmarshal error: %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %v\n\twant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
var streamEncodedIndent = `0.1
|
||||
"hello"
|
||||
null
|
||||
true
|
||||
false
|
||||
[
|
||||
>."a",
|
||||
>."b",
|
||||
>."c"
|
||||
>]
|
||||
{
|
||||
>."ß": "long s",
|
||||
>."K": "Kelvin"
|
||||
>}
|
||||
3.14
|
||||
`
|
||||
|
||||
func TestEncoderIndent(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
enc := NewEncoder(&buf)
|
||||
enc.SetIndent(">", ".")
|
||||
for _, v := range streamTest {
|
||||
enc.Encode(v)
|
||||
}
|
||||
if got, want := buf.String(), streamEncodedIndent; got != want {
|
||||
t.Errorf("Encode mismatch:\ngot:\n%s\n\nwant:\n%s", got, want)
|
||||
diff(t, []byte(got), []byte(want))
|
||||
}
|
||||
}
|
||||
|
||||
type strMarshaler string
|
||||
|
||||
func (s strMarshaler) MarshalJSON() ([]byte, error) {
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
type strPtrMarshaler string
|
||||
|
||||
func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) {
|
||||
return []byte(*s), nil
|
||||
}
|
||||
|
||||
func TestEncoderSetEscapeHTML(t *testing.T) {
|
||||
var c C
|
||||
var ct CText
|
||||
var tagStruct struct {
|
||||
Valid int `json:"<>&#! "`
|
||||
Invalid int `json:"\\"`
|
||||
}
|
||||
|
||||
// This case is particularly interesting, as we force the encoder to
|
||||
// take the address of the Ptr field to use its MarshalJSON method. This
|
||||
// is why the '&' is important.
|
||||
marshalerStruct := &struct {
|
||||
NonPtr strMarshaler
|
||||
Ptr strPtrMarshaler
|
||||
}{`"<str>"`, `"<str>"`}
|
||||
|
||||
// https://golang.org/issue/34154
|
||||
stringOption := struct {
|
||||
Bar string `json:"bar,string"`
|
||||
}{`<html>foobar</html>`}
|
||||
|
||||
tests := []struct {
|
||||
CaseName
|
||||
v any
|
||||
wantEscape string
|
||||
want string
|
||||
}{
|
||||
{Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`},
|
||||
{Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
|
||||
{Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
|
||||
{
|
||||
Name("tagStruct"), tagStruct,
|
||||
`{"\u003c\u003e\u0026#! ":0,"Invalid":0}`,
|
||||
`{"<>&#! ":0,"Invalid":0}`,
|
||||
},
|
||||
{
|
||||
Name(`"<str>"`), marshalerStruct,
|
||||
`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`,
|
||||
`{"NonPtr":"<str>","Ptr":"<str>"}`,
|
||||
},
|
||||
{
|
||||
Name("stringOption"), stringOption,
|
||||
`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`,
|
||||
`{"bar":"\"<html>foobar</html>\""}`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
enc := NewEncoder(&buf)
|
||||
if err := enc.Encode(tt.v); err != nil {
|
||||
t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err)
|
||||
}
|
||||
if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
|
||||
t.Errorf("%s: Encode(%s):\n\tgot: %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape)
|
||||
}
|
||||
buf.Reset()
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(tt.v); err != nil {
|
||||
t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err)
|
||||
}
|
||||
if got := strings.TrimSpace(buf.String()); got != tt.want {
|
||||
t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot: %s\n\twant: %s",
|
||||
tt.Where, tt.Name, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoder(t *testing.T) {
|
||||
for i := 0; i <= len(streamTest); i++ {
|
||||
// Use stream without newlines as input,
|
||||
// just to stress the decoder even more.
|
||||
// Our test input does not include back-to-back numbers.
|
||||
// Otherwise stripping the newlines would
|
||||
// merge two adjacent JSON values.
|
||||
var buf bytes.Buffer
|
||||
for _, c := range nlines(streamEncoded, i) {
|
||||
if c != '\n' {
|
||||
buf.WriteRune(c)
|
||||
}
|
||||
}
|
||||
out := make([]any, i)
|
||||
dec := NewDecoder(&buf)
|
||||
for j := range out {
|
||||
if err := dec.Decode(&out[j]); err != nil {
|
||||
t.Fatalf("decode #%d/%d error: %v", j, i, err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(out, streamTest[0:i]) {
|
||||
t.Errorf("decoding %d items: mismatch:", i)
|
||||
for j := range out {
|
||||
if !reflect.DeepEqual(out[j], streamTest[j]) {
|
||||
t.Errorf("#%d:\n\tgot: %v\n\twant: %v", j, out[j], streamTest[j])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoderBuffered(t *testing.T) {
|
||||
r := strings.NewReader(`{"Name": "Gopher"} extra `)
|
||||
var m struct {
|
||||
Name string
|
||||
}
|
||||
d := NewDecoder(r)
|
||||
err := d.Decode(&m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.Name != "Gopher" {
|
||||
t.Errorf("Name = %s, want Gopher", m.Name)
|
||||
}
|
||||
rest, err := io.ReadAll(d.Buffered())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := string(rest), " extra "; got != want {
|
||||
t.Errorf("Remaining = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func nlines(s string, n int) string {
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
for i, c := range s {
|
||||
if c == '\n' {
|
||||
if n--; n == 0 {
|
||||
return s[0 : i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestRawMessage(t *testing.T) {
|
||||
var data struct {
|
||||
X float64
|
||||
Id RawMessage
|
||||
Y float32
|
||||
}
|
||||
const raw = `["\u0056",null]`
|
||||
const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
|
||||
err := Unmarshal([]byte(want), &data)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
if string([]byte(data.Id)) != raw {
|
||||
t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", []byte(data.Id), raw)
|
||||
}
|
||||
got, err := Marshal(&data)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if string(got) != want {
|
||||
t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullRawMessage(t *testing.T) {
|
||||
var data struct {
|
||||
X float64
|
||||
Id RawMessage
|
||||
IdPtr *RawMessage
|
||||
Y float32
|
||||
}
|
||||
const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}`
|
||||
err := Unmarshal([]byte(want), &data)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
if want, got := "null", string(data.Id); want != got {
|
||||
t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", got, want)
|
||||
}
|
||||
if data.IdPtr != nil {
|
||||
t.Fatalf("pointer mismatch: got non-nil, want nil")
|
||||
}
|
||||
got, err := Marshal(&data)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if string(got) != want {
|
||||
t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlocking(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
in string
|
||||
}{
|
||||
{Name(""), `{"x": 1}`},
|
||||
{Name(""), `[1, 2, 3]`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
r, w := net.Pipe()
|
||||
go w.Write([]byte(tt.in))
|
||||
var val any
|
||||
|
||||
// If Decode reads beyond what w.Write writes above,
|
||||
// it will block, and the test will deadlock.
|
||||
if err := NewDecoder(r).Decode(&val); err != nil {
|
||||
t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err)
|
||||
}
|
||||
r.Close()
|
||||
w.Close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type decodeThis struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func TestDecodeInStream(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
json string
|
||||
expTokens []any
|
||||
}{
|
||||
// streaming token cases
|
||||
{CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}},
|
||||
{CaseName: Name(""), json: ` [10] `, expTokens: []any{
|
||||
Delim('['), float64(10), Delim(']')}},
|
||||
{CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{
|
||||
Delim('['), false, float64(10), "b", Delim(']')}},
|
||||
{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
|
||||
Delim('{'), "a", float64(1), Delim('}')}},
|
||||
{CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{
|
||||
Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
|
||||
{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
|
||||
Delim('['),
|
||||
Delim('{'), "a", float64(1), Delim('}'),
|
||||
Delim('{'), "a", float64(2), Delim('}'),
|
||||
Delim(']')}},
|
||||
{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
|
||||
Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
|
||||
Delim('{'), "obj", Delim('['),
|
||||
Delim('{'), "a", float64(1), Delim('}'),
|
||||
Delim(']'), Delim('}')}},
|
||||
|
||||
// streaming tokens with intermittent Decode()
|
||||
{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
|
||||
Delim('{'), "a",
|
||||
decodeThis{float64(1)},
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{
|
||||
Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
Delim(']')}},
|
||||
{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
|
||||
Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
decodeThis{map[string]any{"a": float64(2)}},
|
||||
Delim(']')}},
|
||||
{CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
|
||||
Delim('{'), "obj", Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
Delim(']'), Delim('}')}},
|
||||
|
||||
{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
|
||||
Delim('{'), "obj",
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
|
||||
Delim('{'), "obj",
|
||||
decodeThis{[]any{
|
||||
map[string]any{"a": float64(1)},
|
||||
}},
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
|
||||
Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
decodeThis{&SyntaxError{"expected comma after array element", 11}},
|
||||
}},
|
||||
{CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{
|
||||
Delim('{'), strings.Repeat("a", 513),
|
||||
decodeThis{&SyntaxError{"expected colon after object key", 518}},
|
||||
}},
|
||||
{CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{
|
||||
Delim('{'),
|
||||
&SyntaxError{"invalid character 'a' in string escape code", 3},
|
||||
}},
|
||||
{CaseName: Name(""), json: ` \a`, expTokens: []any{
|
||||
&SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
dec := NewDecoder(strings.NewReader(tt.json))
|
||||
for i, want := range tt.expTokens {
|
||||
var got any
|
||||
var err error
|
||||
|
||||
if dt, ok := want.(decodeThis); ok {
|
||||
want = dt.v
|
||||
err = dec.Decode(&got)
|
||||
} else {
|
||||
got, err = dec.Token()
|
||||
}
|
||||
if errWant, ok := want.(error); ok {
|
||||
if err == nil || !reflect.DeepEqual(err, errWant) {
|
||||
t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant)
|
||||
}
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot: %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test from golang.org/issue/11893
|
||||
func TestHTTPDecoding(t *testing.T) {
|
||||
const raw = `{ "foo": "bar" }`
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(raw))
|
||||
}))
|
||||
defer ts.Close()
|
||||
res, err := http.Get(ts.URL)
|
||||
if err != nil {
|
||||
log.Fatalf("http.Get error: %v", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
foo := struct {
|
||||
Foo string
|
||||
}{}
|
||||
|
||||
d := NewDecoder(res.Body)
|
||||
err = d.Decode(&foo)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
if foo.Foo != "bar" {
|
||||
t.Errorf(`Decode: got %q, want "bar"`, foo.Foo)
|
||||
}
|
||||
|
||||
// make sure we get the EOF the second time
|
||||
err = d.Decode(&foo)
|
||||
if err != io.EOF {
|
||||
t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err)
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type basicLatin2xTag struct {
|
||||
V string `json:"$%-/"`
|
||||
}
|
||||
|
||||
type basicLatin3xTag struct {
|
||||
V string `json:"0123456789"`
|
||||
}
|
||||
|
||||
type basicLatin4xTag struct {
|
||||
V string `json:"ABCDEFGHIJKLMO"`
|
||||
}
|
||||
|
||||
type basicLatin5xTag struct {
|
||||
V string `json:"PQRSTUVWXYZ_"`
|
||||
}
|
||||
|
||||
type basicLatin6xTag struct {
|
||||
V string `json:"abcdefghijklmno"`
|
||||
}
|
||||
|
||||
type basicLatin7xTag struct {
|
||||
V string `json:"pqrstuvwxyz"`
|
||||
}
|
||||
|
||||
type miscPlaneTag struct {
|
||||
V string `json:"色は匂へど"`
|
||||
}
|
||||
|
||||
type percentSlashTag struct {
|
||||
V string `json:"text/html%"` // https://golang.org/issue/2718
|
||||
}
|
||||
|
||||
type punctuationTag struct {
|
||||
V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` // https://golang.org/issue/3546
|
||||
}
|
||||
|
||||
type dashTag struct {
|
||||
V string `json:"-,"`
|
||||
}
|
||||
|
||||
type emptyTag struct {
|
||||
W string
|
||||
}
|
||||
|
||||
type misnamedTag struct {
|
||||
X string `jsom:"Misnamed"`
|
||||
}
|
||||
|
||||
type badFormatTag struct {
|
||||
Y string `:"BadFormat"`
|
||||
}
|
||||
|
||||
type badCodeTag struct {
|
||||
Z string `json:" !\"#&'()*+,."`
|
||||
}
|
||||
|
||||
type spaceTag struct {
|
||||
Q string `json:"With space"`
|
||||
}
|
||||
|
||||
type unicodeTag struct {
|
||||
W string `json:"Ελλάδα"`
|
||||
}
|
||||
|
||||
func TestStructTagObjectKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
raw any
|
||||
value string
|
||||
key string
|
||||
}{
|
||||
{Name(""), basicLatin2xTag{"2x"}, "2x", "$%-/"},
|
||||
{Name(""), basicLatin3xTag{"3x"}, "3x", "0123456789"},
|
||||
{Name(""), basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"},
|
||||
{Name(""), basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"},
|
||||
{Name(""), basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"},
|
||||
{Name(""), basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"},
|
||||
{Name(""), miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"},
|
||||
{Name(""), dashTag{"foo"}, "foo", "-"},
|
||||
{Name(""), emptyTag{"Pour Moi"}, "Pour Moi", "W"},
|
||||
{Name(""), misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"},
|
||||
{Name(""), badFormatTag{"Orfevre"}, "Orfevre", "Y"},
|
||||
{Name(""), badCodeTag{"Reliable Man"}, "Reliable Man", "Z"},
|
||||
{Name(""), percentSlashTag{"brut"}, "brut", "text/html%"},
|
||||
{Name(""), punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "},
|
||||
{Name(""), spaceTag{"Perreddu"}, "Perreddu", "With space"},
|
||||
{Name(""), unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
b, err := Marshal(tt.raw)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Marshal error: %v", tt.Where, err)
|
||||
}
|
||||
var f any
|
||||
err = Unmarshal(b, &f)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Unmarshal error: %v", tt.Where, err)
|
||||
}
|
||||
for k, v := range f.(map[string]any) {
|
||||
if k == tt.key {
|
||||
if s, ok := v.(string); !ok || s != tt.value {
|
||||
t.Fatalf("%s: Unmarshal(%#q) value:\n\tgot: %q\n\twant: %q", tt.Where, b, s, tt.value)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("%s: Unmarshal(%#q): unexpected key: %q", tt.Where, b, k)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTagParsing(t *testing.T) {
|
||||
name, opts := parseTag("field,foobar,foo")
|
||||
if name != "field" {
|
||||
t.Fatalf("name = %q, want field", name)
|
||||
}
|
||||
for _, tt := range []struct {
|
||||
opt string
|
||||
want bool
|
||||
}{
|
||||
{"foobar", true},
|
||||
{"foo", true},
|
||||
{"bar", false},
|
||||
} {
|
||||
if opts.Contains(tt.opt) != tt.want {
|
||||
t.Errorf("Contains(%q) = %v, want %v", tt.opt, !tt.want, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,483 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
// Large data benchmark.
|
||||
// The JSON data is a summary of agl's changes in the
|
||||
// go, webkit, and chromium open source projects.
|
||||
// We benchmark converting between the JSON form
|
||||
// and in-memory data structures.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"encoding/json/internal/jsontest"
|
||||
)
|
||||
|
||||
type codeResponse struct {
|
||||
Tree *codeNode `json:"tree"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type codeNode struct {
|
||||
Name string `json:"name"`
|
||||
Kids []*codeNode `json:"kids"`
|
||||
CLWeight float64 `json:"cl_weight"`
|
||||
Touches int `json:"touches"`
|
||||
MinT int64 `json:"min_t"`
|
||||
MaxT int64 `json:"max_t"`
|
||||
MeanT int64 `json:"mean_t"`
|
||||
}
|
||||
|
||||
var codeJSON []byte
|
||||
var codeStruct codeResponse
|
||||
|
||||
func codeInit() {
|
||||
var data []byte
|
||||
for _, entry := range jsontest.Data {
|
||||
if entry.Name == "GolangSource" {
|
||||
data = entry.Data()
|
||||
}
|
||||
}
|
||||
codeJSON = data
|
||||
|
||||
if err := Unmarshal(codeJSON, &codeStruct); err != nil {
|
||||
panic("unmarshal code.json: " + err.Error())
|
||||
}
|
||||
|
||||
var err error
|
||||
if data, err = Marshal(&codeStruct); err != nil {
|
||||
panic("marshal code.json: " + err.Error())
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, codeJSON) {
|
||||
println("different lengths", len(data), len(codeJSON))
|
||||
for i := 0; i < len(data) && i < len(codeJSON); i++ {
|
||||
if data[i] != codeJSON[i] {
|
||||
println("re-marshal: changed at byte", i)
|
||||
println("orig: ", string(codeJSON[i-10:i+10]))
|
||||
println("new: ", string(data[i-10:i+10]))
|
||||
break
|
||||
}
|
||||
}
|
||||
panic("re-marshal code.json: different result")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCodeEncoder(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
enc := NewEncoder(io.Discard)
|
||||
for pb.Next() {
|
||||
if err := enc.Encode(&codeStruct); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeEncoderError(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
enc := NewEncoder(io.Discard)
|
||||
for pb.Next() {
|
||||
if err := enc.Encode(&codeStruct); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
if _, err := Marshal(dummy); err == nil {
|
||||
b.Fatal("Marshal error: got nil, want non-nil")
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeMarshal(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(&codeStruct); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeMarshalError(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(&codeStruct); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if _, err := Marshal(dummy); err == nil {
|
||||
b.Fatal("Marshal error: got nil, want non-nil")
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func benchMarshalBytes(n int) func(*testing.B) {
|
||||
sample := []byte("hello world")
|
||||
// Use a struct pointer, to avoid an allocation when passing it as an
|
||||
// interface parameter to Marshal.
|
||||
v := &struct {
|
||||
Bytes []byte
|
||||
}{
|
||||
bytes.Repeat(sample, (n/len(sample))+1)[:n],
|
||||
}
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := Marshal(v); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchMarshalBytesError(n int) func(*testing.B) {
|
||||
sample := []byte("hello world")
|
||||
// Use a struct pointer, to avoid an allocation when passing it as an
|
||||
// interface parameter to Marshal.
|
||||
v := &struct {
|
||||
Bytes []byte
|
||||
}{
|
||||
bytes.Repeat(sample, (n/len(sample))+1)[:n],
|
||||
}
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := Marshal(v); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if _, err := Marshal(dummy); err == nil {
|
||||
b.Fatal("Marshal error: got nil, want non-nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalBytes(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
// 32 fits within encodeState.scratch.
|
||||
b.Run("32", benchMarshalBytes(32))
|
||||
// 256 doesn't fit in encodeState.scratch, but is small enough to
|
||||
// allocate and avoid the slower base64.NewEncoder.
|
||||
b.Run("256", benchMarshalBytes(256))
|
||||
// 4096 is large enough that we want to avoid allocating for it.
|
||||
b.Run("4096", benchMarshalBytes(4096))
|
||||
}
|
||||
|
||||
func BenchmarkMarshalBytesError(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
// 32 fits within encodeState.scratch.
|
||||
b.Run("32", benchMarshalBytesError(32))
|
||||
// 256 doesn't fit in encodeState.scratch, but is small enough to
|
||||
// allocate and avoid the slower base64.NewEncoder.
|
||||
b.Run("256", benchMarshalBytesError(256))
|
||||
// 4096 is large enough that we want to avoid allocating for it.
|
||||
b.Run("4096", benchMarshalBytesError(4096))
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMap(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
m := map[string]int{
|
||||
"key3": 3,
|
||||
"key2": 2,
|
||||
"key1": 1,
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(m); err != nil {
|
||||
b.Fatal("Marshal:", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCodeDecoder(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var buf bytes.Buffer
|
||||
dec := NewDecoder(&buf)
|
||||
var r codeResponse
|
||||
for pb.Next() {
|
||||
buf.Write(codeJSON)
|
||||
// hide EOF
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteByte('\n')
|
||||
if err := dec.Decode(&r); err != nil {
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkUnicodeDecoder(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := []byte(`"\uD83D\uDE01"`)
|
||||
b.SetBytes(int64(len(j)))
|
||||
r := bytes.NewReader(j)
|
||||
dec := NewDecoder(r)
|
||||
var out string
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := dec.Decode(&out); err != nil {
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
r.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecoderStream(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.StopTimer()
|
||||
var buf bytes.Buffer
|
||||
dec := NewDecoder(&buf)
|
||||
buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n")
|
||||
var x any
|
||||
if err := dec.Decode(&x); err != nil {
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
ones := strings.Repeat(" 1\n", 300000) + "\n\n\n"
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%300000 == 0 {
|
||||
buf.WriteString(ones)
|
||||
}
|
||||
x = nil
|
||||
switch err := dec.Decode(&x); {
|
||||
case err != nil:
|
||||
b.Fatalf("Decode error: %v", err)
|
||||
case x != 1.0:
|
||||
b.Fatalf("Decode: got %v want 1.0", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCodeUnmarshal(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var r codeResponse
|
||||
if err := Unmarshal(codeJSON, &r); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkCodeUnmarshalReuse(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
codeInit()
|
||||
b.StartTimer()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var r codeResponse
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(codeJSON, &r); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalString(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`"hello, world"`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var s string
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &s); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalFloat64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`3.14`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var f float64
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &f); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalInt64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`3`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var x int64
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &x); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalMap(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(`{"key1":"value1","key2":"value2","key3":"value3"}`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
x := make(map[string]string, 3)
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(data, &x); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIssue10335(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := []byte(`{"a":{ }}`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var s struct{}
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(j, &s); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIssue34127(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := struct {
|
||||
Bar string `json:"bar,string"`
|
||||
}{
|
||||
Bar: `foobar`,
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if _, err := Marshal(&j); err != nil {
|
||||
b.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmapped(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var s struct{}
|
||||
for pb.Next() {
|
||||
if err := Unmarshal(j, &s); err != nil {
|
||||
b.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkEncodeMarshaler(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
m := struct {
|
||||
A int
|
||||
B RawMessage
|
||||
}{}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
enc := NewEncoder(io.Discard)
|
||||
|
||||
for pb.Next() {
|
||||
if err := enc.Encode(&m); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkEncoderEncode(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
type T struct {
|
||||
X, Y string
|
||||
}
|
||||
v := &T{"foo", "bar"}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := NewEncoder(io.Discard).Encode(v); err != nil {
|
||||
b.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,76 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Animal int
|
||||
|
||||
const (
|
||||
Unknown Animal = iota
|
||||
Gopher
|
||||
Zebra
|
||||
)
|
||||
|
||||
func (a *Animal) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
switch strings.ToLower(s) {
|
||||
default:
|
||||
*a = Unknown
|
||||
case "gopher":
|
||||
*a = Gopher
|
||||
case "zebra":
|
||||
*a = Zebra
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a Animal) MarshalJSON() ([]byte, error) {
|
||||
var s string
|
||||
switch a {
|
||||
default:
|
||||
s = "unknown"
|
||||
case Gopher:
|
||||
s = "gopher"
|
||||
case Zebra:
|
||||
s = "zebra"
|
||||
}
|
||||
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func Example_customMarshalJSON() {
|
||||
blob := `["gopher","armadillo","zebra","unknown","gopher","bee","gopher","zebra"]`
|
||||
var zoo []Animal
|
||||
if err := json.Unmarshal([]byte(blob), &zoo); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
census := make(map[Animal]int)
|
||||
for _, animal := range zoo {
|
||||
census[animal] += 1
|
||||
}
|
||||
|
||||
fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n",
|
||||
census[Gopher], census[Zebra], census[Unknown])
|
||||
|
||||
// Output:
|
||||
// Zoo Census:
|
||||
// * Gophers: 3
|
||||
// * Zebras: 2
|
||||
// * Unknown: 3
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func ExampleMarshal() {
|
||||
type ColorGroup struct {
|
||||
ID int
|
||||
Name string
|
||||
Colors []string
|
||||
}
|
||||
group := ColorGroup{
|
||||
ID: 1,
|
||||
Name: "Reds",
|
||||
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
|
||||
}
|
||||
b, err := json.Marshal(group)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
// Output:
|
||||
// {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
|
||||
}
|
||||
|
||||
func ExampleUnmarshal() {
|
||||
var jsonBlob = []byte(`[
|
||||
{"Name": "Platypus", "Order": "Monotremata"},
|
||||
{"Name": "Quoll", "Order": "Dasyuromorphia"}
|
||||
]`)
|
||||
type Animal struct {
|
||||
Name string
|
||||
Order string
|
||||
}
|
||||
var animals []Animal
|
||||
err := json.Unmarshal(jsonBlob, &animals)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
fmt.Printf("%+v", animals)
|
||||
// Output:
|
||||
// [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
|
||||
}
|
||||
|
||||
// This example uses a Decoder to decode a stream of distinct JSON values.
|
||||
func ExampleDecoder() {
|
||||
const jsonStream = `
|
||||
{"Name": "Ed", "Text": "Knock knock."}
|
||||
{"Name": "Sam", "Text": "Who's there?"}
|
||||
{"Name": "Ed", "Text": "Go fmt."}
|
||||
{"Name": "Sam", "Text": "Go fmt who?"}
|
||||
{"Name": "Ed", "Text": "Go fmt yourself!"}
|
||||
`
|
||||
type Message struct {
|
||||
Name, Text string
|
||||
}
|
||||
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||
for {
|
||||
var m Message
|
||||
if err := dec.Decode(&m); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s: %s\n", m.Name, m.Text)
|
||||
}
|
||||
// Output:
|
||||
// Ed: Knock knock.
|
||||
// Sam: Who's there?
|
||||
// Ed: Go fmt.
|
||||
// Sam: Go fmt who?
|
||||
// Ed: Go fmt yourself!
|
||||
}
|
||||
|
||||
// This example uses a Decoder to decode a stream of distinct JSON values.
|
||||
func ExampleDecoder_Token() {
|
||||
const jsonStream = `
|
||||
{"Message": "Hello", "Array": [1, 2, 3], "Null": null, "Number": 1.234}
|
||||
`
|
||||
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||
for {
|
||||
t, err := dec.Token()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%T: %v", t, t)
|
||||
if dec.More() {
|
||||
fmt.Printf(" (more)")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
// Output:
|
||||
// json.Delim: { (more)
|
||||
// string: Message (more)
|
||||
// string: Hello (more)
|
||||
// string: Array (more)
|
||||
// json.Delim: [ (more)
|
||||
// float64: 1 (more)
|
||||
// float64: 2 (more)
|
||||
// float64: 3
|
||||
// json.Delim: ] (more)
|
||||
// string: Null (more)
|
||||
// <nil>: <nil> (more)
|
||||
// string: Number (more)
|
||||
// float64: 1.234
|
||||
// json.Delim: }
|
||||
}
|
||||
|
||||
// This example uses a Decoder to decode a streaming array of JSON objects.
|
||||
func ExampleDecoder_Decode_stream() {
|
||||
const jsonStream = `
|
||||
[
|
||||
{"Name": "Ed", "Text": "Knock knock."},
|
||||
{"Name": "Sam", "Text": "Who's there?"},
|
||||
{"Name": "Ed", "Text": "Go fmt."},
|
||||
{"Name": "Sam", "Text": "Go fmt who?"},
|
||||
{"Name": "Ed", "Text": "Go fmt yourself!"}
|
||||
]
|
||||
`
|
||||
type Message struct {
|
||||
Name, Text string
|
||||
}
|
||||
dec := json.NewDecoder(strings.NewReader(jsonStream))
|
||||
|
||||
// read open bracket
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%T: %v\n", t, t)
|
||||
|
||||
// while the array contains values
|
||||
for dec.More() {
|
||||
var m Message
|
||||
// decode an array value (Message)
|
||||
err := dec.Decode(&m)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v: %v\n", m.Name, m.Text)
|
||||
}
|
||||
|
||||
// read closing bracket
|
||||
t, err = dec.Token()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%T: %v\n", t, t)
|
||||
|
||||
// Output:
|
||||
// json.Delim: [
|
||||
// Ed: Knock knock.
|
||||
// Sam: Who's there?
|
||||
// Ed: Go fmt.
|
||||
// Sam: Go fmt who?
|
||||
// Ed: Go fmt yourself!
|
||||
// json.Delim: ]
|
||||
}
|
||||
|
||||
// This example uses RawMessage to delay parsing part of a JSON message.
|
||||
func ExampleRawMessage_unmarshal() {
|
||||
type Color struct {
|
||||
Space string
|
||||
Point json.RawMessage // delay parsing until we know the color space
|
||||
}
|
||||
type RGB struct {
|
||||
R uint8
|
||||
G uint8
|
||||
B uint8
|
||||
}
|
||||
type YCbCr struct {
|
||||
Y uint8
|
||||
Cb int8
|
||||
Cr int8
|
||||
}
|
||||
|
||||
var j = []byte(`[
|
||||
{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
|
||||
{"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}}
|
||||
]`)
|
||||
var colors []Color
|
||||
err := json.Unmarshal(j, &colors)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
|
||||
for _, c := range colors {
|
||||
var dst any
|
||||
switch c.Space {
|
||||
case "RGB":
|
||||
dst = new(RGB)
|
||||
case "YCbCr":
|
||||
dst = new(YCbCr)
|
||||
}
|
||||
err := json.Unmarshal(c.Point, dst)
|
||||
if err != nil {
|
||||
log.Fatalln("error:", err)
|
||||
}
|
||||
fmt.Println(c.Space, dst)
|
||||
}
|
||||
// Output:
|
||||
// YCbCr &{255 0 -10}
|
||||
// RGB &{98 218 255}
|
||||
}
|
||||
|
||||
// This example uses RawMessage to use a precomputed JSON during marshal.
|
||||
func ExampleRawMessage_marshal() {
|
||||
h := json.RawMessage(`{"precomputed": true}`)
|
||||
|
||||
c := struct {
|
||||
Header *json.RawMessage `json:"header"`
|
||||
Body string `json:"body"`
|
||||
}{Header: &h, Body: "Hello Gophers!"}
|
||||
|
||||
b, err := json.MarshalIndent(&c, "", "\t")
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
|
||||
// Output:
|
||||
// {
|
||||
// "header": {
|
||||
// "precomputed": true
|
||||
// },
|
||||
// "body": "Hello Gophers!"
|
||||
// }
|
||||
}
|
||||
|
||||
func ExampleIndent() {
|
||||
type Road struct {
|
||||
Name string
|
||||
Number int
|
||||
}
|
||||
roads := []Road{
|
||||
{"Diamond Fork", 29},
|
||||
{"Sheep Creek", 51},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(roads)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
json.Indent(&out, b, "=", "\t")
|
||||
out.WriteTo(os.Stdout)
|
||||
// Output:
|
||||
// [
|
||||
// = {
|
||||
// = "Name": "Diamond Fork",
|
||||
// = "Number": 29
|
||||
// = },
|
||||
// = {
|
||||
// = "Name": "Sheep Creek",
|
||||
// = "Number": 51
|
||||
// = }
|
||||
// =]
|
||||
}
|
||||
|
||||
func ExampleMarshalIndent() {
|
||||
data := map[string]int{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(data, "<prefix>", "<indent>")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
// Output:
|
||||
// {
|
||||
// <prefix><indent>"a": 1,
|
||||
// <prefix><indent>"b": 2
|
||||
// <prefix>}
|
||||
}
|
||||
|
||||
func ExampleValid() {
|
||||
goodJSON := `{"example": 1}`
|
||||
badJSON := `{"example":2:]}}`
|
||||
|
||||
fmt.Println(json.Valid([]byte(goodJSON)), json.Valid([]byte(badJSON)))
|
||||
// Output:
|
||||
// true false
|
||||
}
|
||||
|
||||
func ExampleHTMLEscape() {
|
||||
var out bytes.Buffer
|
||||
json.HTMLEscape(&out, []byte(`{"Name":"<b>HTML content</b>"}`))
|
||||
out.WriteTo(os.Stdout)
|
||||
// Output:
|
||||
//{"Name":"\u003cb\u003eHTML content\u003c/b\u003e"}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Size int
|
||||
|
||||
const (
|
||||
Unrecognized Size = iota
|
||||
Small
|
||||
Large
|
||||
)
|
||||
|
||||
func (s *Size) UnmarshalText(text []byte) error {
|
||||
switch strings.ToLower(string(text)) {
|
||||
default:
|
||||
*s = Unrecognized
|
||||
case "small":
|
||||
*s = Small
|
||||
case "large":
|
||||
*s = Large
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Size) MarshalText() ([]byte, error) {
|
||||
var name string
|
||||
switch s {
|
||||
default:
|
||||
name = "unrecognized"
|
||||
case Small:
|
||||
name = "small"
|
||||
case Large:
|
||||
name = "large"
|
||||
}
|
||||
return []byte(name), nil
|
||||
}
|
||||
|
||||
func Example_textMarshalJSON() {
|
||||
blob := `["small","regular","large","unrecognized","small","normal","small","large"]`
|
||||
var inventory []Size
|
||||
if err := json.Unmarshal([]byte(blob), &inventory); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
counts := make(map[Size]int)
|
||||
for _, size := range inventory {
|
||||
counts[size] += 1
|
||||
}
|
||||
|
||||
fmt.Printf("Inventory Counts:\n* Small: %d\n* Large: %d\n* Unrecognized: %d\n",
|
||||
counts[Small], counts[Large], counts[Unrecognized])
|
||||
|
||||
// Output:
|
||||
// Inventory Counts:
|
||||
// * Small: 3
|
||||
// * Large: 2
|
||||
// * Unrecognized: 3
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzUnmarshalJSON(f *testing.F) {
|
||||
f.Add([]byte(`{
|
||||
"object": {
|
||||
"slice": [
|
||||
1,
|
||||
2.0,
|
||||
"3",
|
||||
[4],
|
||||
{5: {}}
|
||||
]
|
||||
},
|
||||
"slice": [[]],
|
||||
"string": ":)",
|
||||
"int": 1e5,
|
||||
"float": 3e-9"
|
||||
}`))
|
||||
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
for _, typ := range []func() any{
|
||||
func() any { return new(any) },
|
||||
func() any { return new(map[string]any) },
|
||||
func() any { return new([]any) },
|
||||
} {
|
||||
i := typ()
|
||||
if err := Unmarshal(b, i); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encoded, err := Marshal(i)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %s", err)
|
||||
}
|
||||
|
||||
if err := Unmarshal(encoded, i); err != nil {
|
||||
t.Fatalf("failed to roundtrip: %s", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDecoderToken(f *testing.F) {
|
||||
f.Add([]byte(`{
|
||||
"object": {
|
||||
"slice": [
|
||||
1,
|
||||
2.0,
|
||||
"3",
|
||||
[4],
|
||||
{5: {}}
|
||||
]
|
||||
},
|
||||
"slice": [[]],
|
||||
"string": ":)",
|
||||
"int": 1e5,
|
||||
"float": 3e-9"
|
||||
}`))
|
||||
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
r := bytes.NewReader(b)
|
||||
d := NewDecoder(r)
|
||||
for {
|
||||
_, err := d.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func indentNewlines(s string) string {
|
||||
return strings.Join(strings.Split(s, "\n"), "\n\t")
|
||||
}
|
||||
|
||||
func stripWhitespace(s string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if r == ' ' || r == '\n' || r == '\r' || r == '\t' {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, s)
|
||||
}
|
||||
|
||||
func TestValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
data string
|
||||
ok bool
|
||||
}{
|
||||
{Name(""), `foo`, false},
|
||||
{Name(""), `}{`, false},
|
||||
{Name(""), `{]`, false},
|
||||
{Name(""), `{}`, true},
|
||||
{Name(""), `{"foo":"bar"}`, true},
|
||||
{Name(""), `{"foo":"bar","bar":{"baz":["qux"]}}`, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
if ok := Valid([]byte(tt.data)); ok != tt.ok {
|
||||
t.Errorf("%s: Valid(`%s`) = %v, want %v", tt.Where, tt.data, ok, tt.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactAndIndent(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
compact string
|
||||
indent string
|
||||
}{
|
||||
{Name(""), `1`, `1`},
|
||||
{Name(""), `{}`, `{}`},
|
||||
{Name(""), `[]`, `[]`},
|
||||
{Name(""), `{"":2}`, "{\n\t\"\": 2\n}"},
|
||||
{Name(""), `[3]`, "[\n\t3\n]"},
|
||||
{Name(""), `[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"},
|
||||
{Name(""), `{"x":1}`, "{\n\t\"x\": 1\n}"},
|
||||
{Name(""), `[true,false,null,"x",1,1.5,0,-5e+2]`, `[
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
"x",
|
||||
1,
|
||||
1.5,
|
||||
0,
|
||||
-5e+2
|
||||
]`},
|
||||
{Name(""), "{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
buf.Reset()
|
||||
if err := Compact(&buf, []byte(tt.compact)); err != nil {
|
||||
t.Errorf("%s: Compact error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.compact {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := Compact(&buf, []byte(tt.indent)); err != nil {
|
||||
t.Errorf("%s: Compact error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.compact {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil {
|
||||
t.Errorf("%s: Indent error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.indent {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil {
|
||||
t.Errorf("%s: Indent error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.indent {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.indent))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactSeparators(t *testing.T) {
|
||||
// U+2028 and U+2029 should be escaped inside strings.
|
||||
// They should not appear outside strings.
|
||||
tests := []struct {
|
||||
CaseName
|
||||
in, compact string
|
||||
}{
|
||||
{Name(""), "{\"\u2028\": 1}", "{\"\u2028\":1}"},
|
||||
{Name(""), "{\"\u2029\" :2}", "{\"\u2029\":2}"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := Compact(&buf, []byte(tt.in)); err != nil {
|
||||
t.Errorf("%s: Compact error: %v", tt.Where, err)
|
||||
} else if got := buf.String(); got != tt.compact {
|
||||
t.Errorf("%s: Compact:\n\tgot: %s\n\twant: %s", tt.Where, indentNewlines(got), indentNewlines(tt.compact))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of a large random structure.
|
||||
|
||||
func TestCompactBig(t *testing.T) {
|
||||
initBig()
|
||||
var buf bytes.Buffer
|
||||
if err := Compact(&buf, jsonBig); err != nil {
|
||||
t.Fatalf("Compact error: %v", err)
|
||||
}
|
||||
b := buf.Bytes()
|
||||
if !bytes.Equal(b, jsonBig) {
|
||||
t.Error("Compact:")
|
||||
diff(t, b, jsonBig)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndentBig(t *testing.T) {
|
||||
t.Parallel()
|
||||
initBig()
|
||||
var buf bytes.Buffer
|
||||
if err := Indent(&buf, jsonBig, "", "\t"); err != nil {
|
||||
t.Fatalf("Indent error: %v", err)
|
||||
}
|
||||
b := buf.Bytes()
|
||||
if len(b) == len(jsonBig) {
|
||||
// jsonBig is compact (no unnecessary spaces);
|
||||
// indenting should make it bigger
|
||||
t.Fatalf("Indent did not expand the input")
|
||||
}
|
||||
|
||||
// should be idempotent
|
||||
var buf1 bytes.Buffer
|
||||
if err := Indent(&buf1, b, "", "\t"); err != nil {
|
||||
t.Fatalf("Indent error: %v", err)
|
||||
}
|
||||
b1 := buf1.Bytes()
|
||||
if !bytes.Equal(b1, b) {
|
||||
t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig):")
|
||||
diff(t, b1, b)
|
||||
return
|
||||
}
|
||||
|
||||
// should get back to original
|
||||
buf1.Reset()
|
||||
if err := Compact(&buf1, b); err != nil {
|
||||
t.Fatalf("Compact error: %v", err)
|
||||
}
|
||||
b1 = buf1.Bytes()
|
||||
if !bytes.Equal(b1, jsonBig) {
|
||||
t.Error("Compact(Indent(jsonBig)) != jsonBig:")
|
||||
diff(t, b1, jsonBig)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndentErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
in string
|
||||
err error
|
||||
}{
|
||||
{Name(""), `{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", len64(`{"X": "foo", "Y"`)}},
|
||||
{Name(""), `{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", len64(`{"X": "foo" `)}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
slice := make([]uint8, 0)
|
||||
buf := bytes.NewBuffer(slice)
|
||||
if err := Indent(buf, []uint8(tt.in), "", ""); err != nil {
|
||||
if !reflect.DeepEqual(err, tt.err) {
|
||||
t.Fatalf("%s: Indent error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func diff(t *testing.T, a, b []byte) {
|
||||
t.Helper()
|
||||
for i := 0; ; i++ {
|
||||
if i >= len(a) || i >= len(b) || a[i] != b[i] {
|
||||
j := i - 10
|
||||
if j < 0 {
|
||||
j = 0
|
||||
}
|
||||
t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:]))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func trim(b []byte) []byte {
|
||||
return b[:min(len(b), 20)]
|
||||
}
|
||||
|
||||
// Generate a random JSON object.
|
||||
|
||||
var jsonBig []byte
|
||||
|
||||
func initBig() {
|
||||
n := 10000
|
||||
if testing.Short() {
|
||||
n = 100
|
||||
}
|
||||
b, err := Marshal(genValue(n))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
jsonBig = b
|
||||
}
|
||||
|
||||
func genValue(n int) any {
|
||||
if n > 1 {
|
||||
switch rand.Intn(2) {
|
||||
case 0:
|
||||
return genArray(n)
|
||||
case 1:
|
||||
return genMap(n)
|
||||
}
|
||||
}
|
||||
switch rand.Intn(3) {
|
||||
case 0:
|
||||
return rand.Intn(2) == 0
|
||||
case 1:
|
||||
return rand.NormFloat64()
|
||||
case 2:
|
||||
return genString(30)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func genString(stddev float64) string {
|
||||
n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2))
|
||||
c := make([]rune, n)
|
||||
for i := range c {
|
||||
f := math.Abs(rand.NormFloat64()*64 + 32)
|
||||
if f > 0x10ffff {
|
||||
f = 0x10ffff
|
||||
}
|
||||
c[i] = rune(f)
|
||||
}
|
||||
return string(c)
|
||||
}
|
||||
|
||||
func genArray(n int) []any {
|
||||
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
|
||||
if f > n {
|
||||
f = n
|
||||
}
|
||||
if f < 1 {
|
||||
f = 1
|
||||
}
|
||||
x := make([]any, f)
|
||||
for i := range x {
|
||||
x[i] = genValue(((i+1)*n)/f - (i*n)/f)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func genMap(n int) map[string]any {
|
||||
f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2)))
|
||||
if f > n {
|
||||
f = n
|
||||
}
|
||||
if n > 0 && f == 0 {
|
||||
f = 1
|
||||
}
|
||||
x := make(map[string]any)
|
||||
for i := 0; i < f; i++ {
|
||||
x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f)
|
||||
}
|
||||
return x
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"encoding/json/internal/jsontest"
|
||||
)
|
||||
|
||||
type CaseName = jsontest.CaseName
|
||||
type CasePos = jsontest.CasePos
|
||||
|
||||
var Name = jsontest.Name
|
||||
|
||||
// Test values for the stream test.
|
||||
// One of each JSON kind.
|
||||
var streamTest = []any{
|
||||
0.1,
|
||||
"hello",
|
||||
nil,
|
||||
true,
|
||||
false,
|
||||
[]any{"a", "b", "c"},
|
||||
map[string]any{"K": "Kelvin", "ß": "long s"},
|
||||
3.14, // another value to make sure something can follow map
|
||||
}
|
||||
|
||||
var streamEncoded = `0.1
|
||||
"hello"
|
||||
null
|
||||
true
|
||||
false
|
||||
["a","b","c"]
|
||||
{"ß":"long s","K":"Kelvin"}
|
||||
3.14
|
||||
`
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
for i := 0; i <= len(streamTest); i++ {
|
||||
var buf strings.Builder
|
||||
enc := NewEncoder(&buf)
|
||||
// Check that enc.SetIndent("", "") turns off indentation.
|
||||
enc.SetIndent(">", ".")
|
||||
enc.SetIndent("", "")
|
||||
for j, v := range streamTest[0:i] {
|
||||
if err := enc.Encode(v); err != nil {
|
||||
t.Fatalf("#%d.%d Encode error: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
if got, want := buf.String(), nlines(streamEncoded, i); got != want {
|
||||
t.Errorf("encoding %d items: mismatch:", i)
|
||||
diff(t, []byte(got), []byte(want))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderErrorAndReuseEncodeState(t *testing.T) {
|
||||
// Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test.
|
||||
percent := debug.SetGCPercent(-1)
|
||||
defer debug.SetGCPercent(percent)
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
if err := enc.Encode(dummy); err == nil {
|
||||
t.Errorf("Encode(dummy) error: got nil, want non-nil")
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
A string
|
||||
I int
|
||||
}
|
||||
want := Data{A: "a", I: 1}
|
||||
if err := enc.Encode(want); err != nil {
|
||||
t.Errorf("Marshal error: %v", err)
|
||||
}
|
||||
|
||||
var got Data
|
||||
if err := Unmarshal(buf.Bytes(), &got); err != nil {
|
||||
t.Errorf("Unmarshal error: %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %v\n\twant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
var streamEncodedIndent = `0.1
|
||||
"hello"
|
||||
null
|
||||
true
|
||||
false
|
||||
[
|
||||
>."a",
|
||||
>."b",
|
||||
>."c"
|
||||
>]
|
||||
{
|
||||
>."ß": "long s",
|
||||
>."K": "Kelvin"
|
||||
>}
|
||||
3.14
|
||||
`
|
||||
|
||||
func TestEncoderIndent(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
enc := NewEncoder(&buf)
|
||||
enc.SetIndent(">", ".")
|
||||
for _, v := range streamTest {
|
||||
enc.Encode(v)
|
||||
}
|
||||
if got, want := buf.String(), streamEncodedIndent; got != want {
|
||||
t.Errorf("Encode mismatch:\ngot:\n%s\n\nwant:\n%s", got, want)
|
||||
diff(t, []byte(got), []byte(want))
|
||||
}
|
||||
}
|
||||
|
||||
type strMarshaler string
|
||||
|
||||
func (s strMarshaler) MarshalJSON() ([]byte, error) {
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
type strPtrMarshaler string
|
||||
|
||||
func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) {
|
||||
return []byte(*s), nil
|
||||
}
|
||||
|
||||
func TestEncoderSetEscapeHTML(t *testing.T) {
|
||||
var c C
|
||||
var ct CText
|
||||
var tagStruct struct {
|
||||
Valid int `json:"<>&#! "`
|
||||
Invalid int `json:"\\"`
|
||||
}
|
||||
|
||||
// This case is particularly interesting, as we force the encoder to
|
||||
// take the address of the Ptr field to use its MarshalJSON method. This
|
||||
// is why the '&' is important.
|
||||
marshalerStruct := &struct {
|
||||
NonPtr strMarshaler
|
||||
Ptr strPtrMarshaler
|
||||
}{`"<str>"`, `"<str>"`}
|
||||
|
||||
// https://golang.org/issue/34154
|
||||
stringOption := struct {
|
||||
Bar string `json:"bar,string"`
|
||||
}{`<html>foobar</html>`}
|
||||
|
||||
tests := []struct {
|
||||
CaseName
|
||||
v any
|
||||
wantEscape string
|
||||
want string
|
||||
}{
|
||||
{Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`},
|
||||
{Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
|
||||
{Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
|
||||
{
|
||||
Name("tagStruct"), tagStruct,
|
||||
`{"\u003c\u003e\u0026#! ":0,"Invalid":0}`,
|
||||
`{"<>&#! ":0,"Invalid":0}`,
|
||||
},
|
||||
{
|
||||
Name(`"<str>"`), marshalerStruct,
|
||||
`{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`,
|
||||
`{"NonPtr":"<str>","Ptr":"<str>"}`,
|
||||
},
|
||||
{
|
||||
Name("stringOption"), stringOption,
|
||||
`{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`,
|
||||
`{"bar":"\"<html>foobar</html>\""}`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
enc := NewEncoder(&buf)
|
||||
if err := enc.Encode(tt.v); err != nil {
|
||||
t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err)
|
||||
}
|
||||
if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
|
||||
t.Errorf("%s: Encode(%s):\n\tgot: %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape)
|
||||
}
|
||||
buf.Reset()
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(tt.v); err != nil {
|
||||
t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err)
|
||||
}
|
||||
if got := strings.TrimSpace(buf.String()); got != tt.want {
|
||||
t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot: %s\n\twant: %s",
|
||||
tt.Where, tt.Name, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoder(t *testing.T) {
|
||||
for i := 0; i <= len(streamTest); i++ {
|
||||
// Use stream without newlines as input,
|
||||
// just to stress the decoder even more.
|
||||
// Our test input does not include back-to-back numbers.
|
||||
// Otherwise stripping the newlines would
|
||||
// merge two adjacent JSON values.
|
||||
var buf bytes.Buffer
|
||||
for _, c := range nlines(streamEncoded, i) {
|
||||
if c != '\n' {
|
||||
buf.WriteRune(c)
|
||||
}
|
||||
}
|
||||
out := make([]any, i)
|
||||
dec := NewDecoder(&buf)
|
||||
for j := range out {
|
||||
if err := dec.Decode(&out[j]); err != nil {
|
||||
t.Fatalf("decode #%d/%d error: %v", j, i, err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(out, streamTest[0:i]) {
|
||||
t.Errorf("decoding %d items: mismatch:", i)
|
||||
for j := range out {
|
||||
if !reflect.DeepEqual(out[j], streamTest[j]) {
|
||||
t.Errorf("#%d:\n\tgot: %v\n\twant: %v", j, out[j], streamTest[j])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoderBuffered(t *testing.T) {
|
||||
r := strings.NewReader(`{"Name": "Gopher"} extra `)
|
||||
var m struct {
|
||||
Name string
|
||||
}
|
||||
d := NewDecoder(r)
|
||||
err := d.Decode(&m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.Name != "Gopher" {
|
||||
t.Errorf("Name = %s, want Gopher", m.Name)
|
||||
}
|
||||
rest, err := io.ReadAll(d.Buffered())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := string(rest), " extra "; got != want {
|
||||
t.Errorf("Remaining = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func nlines(s string, n int) string {
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
for i, c := range s {
|
||||
if c == '\n' {
|
||||
if n--; n == 0 {
|
||||
return s[0 : i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestRawMessage(t *testing.T) {
|
||||
var data struct {
|
||||
X float64
|
||||
Id RawMessage
|
||||
Y float32
|
||||
}
|
||||
const raw = `["\u0056",null]`
|
||||
const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
|
||||
err := Unmarshal([]byte(want), &data)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
if string([]byte(data.Id)) != raw {
|
||||
t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", []byte(data.Id), raw)
|
||||
}
|
||||
got, err := Marshal(&data)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if string(got) != want {
|
||||
t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullRawMessage(t *testing.T) {
|
||||
var data struct {
|
||||
X float64
|
||||
Id RawMessage
|
||||
IdPtr *RawMessage
|
||||
Y float32
|
||||
}
|
||||
const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}`
|
||||
err := Unmarshal([]byte(want), &data)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal error: %v", err)
|
||||
}
|
||||
if want, got := "null", string(data.Id); want != got {
|
||||
t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", got, want)
|
||||
}
|
||||
if data.IdPtr != nil {
|
||||
t.Fatalf("pointer mismatch: got non-nil, want nil")
|
||||
}
|
||||
got, err := Marshal(&data)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal error: %v", err)
|
||||
}
|
||||
if string(got) != want {
|
||||
t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlocking(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
in string
|
||||
}{
|
||||
{Name(""), `{"x": 1}`},
|
||||
{Name(""), `[1, 2, 3]`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
r, w := net.Pipe()
|
||||
go w.Write([]byte(tt.in))
|
||||
var val any
|
||||
|
||||
// If Decode reads beyond what w.Write writes above,
|
||||
// it will block, and the test will deadlock.
|
||||
if err := NewDecoder(r).Decode(&val); err != nil {
|
||||
t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err)
|
||||
}
|
||||
r.Close()
|
||||
w.Close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type decodeThis struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func TestDecodeInStream(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
json string
|
||||
expTokens []any
|
||||
}{
|
||||
// streaming token cases
|
||||
{CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}},
|
||||
{CaseName: Name(""), json: ` [10] `, expTokens: []any{
|
||||
Delim('['), float64(10), Delim(']')}},
|
||||
{CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{
|
||||
Delim('['), false, float64(10), "b", Delim(']')}},
|
||||
{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
|
||||
Delim('{'), "a", float64(1), Delim('}')}},
|
||||
{CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{
|
||||
Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
|
||||
{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
|
||||
Delim('['),
|
||||
Delim('{'), "a", float64(1), Delim('}'),
|
||||
Delim('{'), "a", float64(2), Delim('}'),
|
||||
Delim(']')}},
|
||||
{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
|
||||
Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
|
||||
Delim('{'), "obj", Delim('['),
|
||||
Delim('{'), "a", float64(1), Delim('}'),
|
||||
Delim(']'), Delim('}')}},
|
||||
|
||||
// streaming tokens with intermittent Decode()
|
||||
{CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
|
||||
Delim('{'), "a",
|
||||
decodeThis{float64(1)},
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{
|
||||
Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
Delim(']')}},
|
||||
{CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
|
||||
Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
decodeThis{map[string]any{"a": float64(2)}},
|
||||
Delim(']')}},
|
||||
{CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
|
||||
Delim('{'), "obj", Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
Delim(']'), Delim('}')}},
|
||||
|
||||
{CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
|
||||
Delim('{'), "obj",
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
|
||||
Delim('{'), "obj",
|
||||
decodeThis{[]any{
|
||||
map[string]any{"a": float64(1)},
|
||||
}},
|
||||
Delim('}')}},
|
||||
{CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
|
||||
Delim('['),
|
||||
decodeThis{map[string]any{"a": float64(1)}},
|
||||
decodeThis{&SyntaxError{"invalid character '{' after array element", len64(` [{"a": 1} `)}},
|
||||
}},
|
||||
{CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{
|
||||
Delim('{'), strings.Repeat("a", 513),
|
||||
decodeThis{&SyntaxError{"invalid character '1' after object key", len64(`{ "` + strings.Repeat("a", 513) + `" `)}},
|
||||
}},
|
||||
{CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{
|
||||
Delim('{'),
|
||||
&SyntaxError{"invalid escape sequence `\\a` in string", len64(`{ "`)},
|
||||
}},
|
||||
{CaseName: Name(""), json: ` \a`, expTokens: []any{
|
||||
&SyntaxError{"invalid character '\\\\' looking for beginning of value", len64(` `)},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
dec := NewDecoder(strings.NewReader(tt.json))
|
||||
for i, want := range tt.expTokens {
|
||||
var got any
|
||||
var err error
|
||||
|
||||
if dt, ok := want.(decodeThis); ok {
|
||||
want = dt.v
|
||||
err = dec.Decode(&got)
|
||||
} else {
|
||||
got, err = dec.Token()
|
||||
}
|
||||
if errWant, ok := want.(error); ok {
|
||||
if err == nil || !reflect.DeepEqual(err, errWant) {
|
||||
t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant)
|
||||
}
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot: %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test from golang.org/issue/11893
|
||||
func TestHTTPDecoding(t *testing.T) {
|
||||
const raw = `{ "foo": "bar" }`
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(raw))
|
||||
}))
|
||||
defer ts.Close()
|
||||
res, err := http.Get(ts.URL)
|
||||
if err != nil {
|
||||
log.Fatalf("http.Get error: %v", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
foo := struct {
|
||||
Foo string
|
||||
}{}
|
||||
|
||||
d := NewDecoder(res.Body)
|
||||
err = d.Decode(&foo)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode error: %v", err)
|
||||
}
|
||||
if foo.Foo != "bar" {
|
||||
t.Errorf(`Decode: got %q, want "bar"`, foo.Foo)
|
||||
}
|
||||
|
||||
// make sure we get the EOF the second time
|
||||
err = d.Decode(&foo)
|
||||
if err != io.EOF {
|
||||
t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err)
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import "testing"
|
||||
|
||||
type basicLatin2xTag struct {
|
||||
V string `json:"$%-/"`
|
||||
}
|
||||
|
||||
type basicLatin3xTag struct {
|
||||
V string `json:"0123456789"`
|
||||
}
|
||||
|
||||
type basicLatin4xTag struct {
|
||||
V string `json:"ABCDEFGHIJKLMO"`
|
||||
}
|
||||
|
||||
type basicLatin5xTag struct {
|
||||
V string `json:"PQRSTUVWXYZ_"`
|
||||
}
|
||||
|
||||
type basicLatin6xTag struct {
|
||||
V string `json:"abcdefghijklmno"`
|
||||
}
|
||||
|
||||
type basicLatin7xTag struct {
|
||||
V string `json:"pqrstuvwxyz"`
|
||||
}
|
||||
|
||||
type miscPlaneTag struct {
|
||||
V string `json:"色は匂へど"`
|
||||
}
|
||||
|
||||
type percentSlashTag struct {
|
||||
V string `json:"text/html%"` // https://golang.org/issue/2718
|
||||
}
|
||||
|
||||
type punctuationTag struct {
|
||||
V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` // https://golang.org/issue/3546
|
||||
}
|
||||
|
||||
type dashTag struct {
|
||||
V string `json:"-,"`
|
||||
}
|
||||
|
||||
type emptyTag struct {
|
||||
W string
|
||||
}
|
||||
|
||||
type misnamedTag struct {
|
||||
X string `jsom:"Misnamed"`
|
||||
}
|
||||
|
||||
type badFormatTag struct {
|
||||
Y string `:"BadFormat"`
|
||||
}
|
||||
|
||||
type badCodeTag struct {
|
||||
Z string `json:" !\"#&'()*+,."`
|
||||
}
|
||||
|
||||
type spaceTag struct {
|
||||
Q string `json:"With space"`
|
||||
}
|
||||
|
||||
type unicodeTag struct {
|
||||
W string `json:"Ελλάδα"`
|
||||
}
|
||||
|
||||
func TestStructTagObjectKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
CaseName
|
||||
raw any
|
||||
value string
|
||||
key string
|
||||
}{
|
||||
{Name(""), basicLatin2xTag{"2x"}, "2x", "$%-/"},
|
||||
{Name(""), basicLatin3xTag{"3x"}, "3x", "0123456789"},
|
||||
{Name(""), basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"},
|
||||
{Name(""), basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"},
|
||||
{Name(""), basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"},
|
||||
{Name(""), basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"},
|
||||
{Name(""), miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"},
|
||||
{Name(""), dashTag{"foo"}, "foo", "-"},
|
||||
{Name(""), emptyTag{"Pour Moi"}, "Pour Moi", "W"},
|
||||
{Name(""), misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"},
|
||||
{Name(""), badFormatTag{"Orfevre"}, "Orfevre", "Y"},
|
||||
{Name(""), badCodeTag{"Reliable Man"}, "Reliable Man", "Z"},
|
||||
{Name(""), percentSlashTag{"brut"}, "brut", "text/html%"},
|
||||
{Name(""), punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "},
|
||||
{Name(""), spaceTag{"Perreddu"}, "Perreddu", "With space"},
|
||||
{Name(""), unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
b, err := Marshal(tt.raw)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Marshal error: %v", tt.Where, err)
|
||||
}
|
||||
var f any
|
||||
err = Unmarshal(b, &f)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Unmarshal error: %v", tt.Where, err)
|
||||
}
|
||||
for k, v := range f.(map[string]any) {
|
||||
if k == tt.key {
|
||||
if s, ok := v.(string); !ok || s != tt.value {
|
||||
t.Fatalf("%s: Unmarshal(%#q) value:\n\tgot: %q\n\twant: %q", tt.Where, b, s, tt.value)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("%s: Unmarshal(%#q): unexpected key: %q", tt.Where, b, k)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
23
pkg/utils/pointers/pointers.go
Normal file
23
pkg/utils/pointers/pointers.go
Normal 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
|
||||
}
|
||||
95
pkg/utils/values/values.go
Normal file
95
pkg/utils/values/values.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user