mirror of
https://github.com/golang/go.git
synced 2026-06-28 03:40:37 +00:00
encoding/json/internal/jsonwire: remove generic implementations
It is common that JSON text is in both ~[]byte and ~string forms,
which is why many jsonwire functions are parameterized on such types.
However, this leads to an implementation challenge since the
utf8 package itself does not provide generic functionality
that take in the same parameterized arguments.
See #56948
At the time when the jsonwire functionality was written
back in March 9th, 2023, there was no allocation-free way to convert
a string into a []byte. At best, you could convert a short string
into a []byte, and it would be stack-allocated if it did not escape.
Thus, the truncateMaxUTF8 helper function would truncate a
byte sequence to ensure it was no longer than 4 bytes,
so that blindly converting it to a string would not heap allocate
(but would still incur a stack allocation).
See 1d04011974
However, with https://go.dev/cl/520395, which first became available in Go 1.22,
it is now possible to zero-copy convert a string to a []byte
if the []byte does not escape and is not mutated.
These properties hold for all the functionality that we care about
where things operates on ~[]byte and ~string.
See #2205
Consequentely, we can now delete the truncateMaxUTF8 hack and
instead convert string to []byte without performance penalties.
This results in both a slight reduction in binary size
where json.test drops by ~11KiB on GOARCH=amd64 and GOOS=linux
since we no longer need to link in two copies of the same logic.
Also, the benchmarks improve by up to ~8% for testdata heavy with non-ASCII,
while exhibiting no changes to the number of heap allocations.
Given the optimization in CL 520395, we could remove the parameterization
of the AppendQuote and AppendUnquote functions,
but there is still a ergonomic reason to keep it.
Users do not need to cast ~string types to []byte whenever calling them.
The conversion makes it look like an allocation is occuring when it is not,
bringing unnecessary review scrutiny.
Performance:
name old speed new speed delta
Testdata/CanadaGeometry/Marshal/Concrete 340MB/s ±11% 354MB/s ± 6% +4.05% (p=0.011 n=19+19)
Testdata/CanadaGeometry/Unmarshal/Concrete 234MB/s ±11% 234MB/s ±10% ~ (p=0.901 n=20+19)
Testdata/CitmCatalog/Marshal/Concrete 2.15GB/s ± 6% 2.10GB/s ± 5% -2.48% (p=0.007 n=19+18)
Testdata/CitmCatalog/Unmarshal/Concrete 827MB/s ± 5% 872MB/s ± 5% +5.45% (p=0.000 n=19+18)
Testdata/GolangSource/Marshal/Concrete 661MB/s ± 6% 648MB/s ± 9% ~ (p=0.149 n=19+20)
Testdata/GolangSource/Unmarshal/Concrete 329MB/s ± 6% 354MB/s ± 4% +7.69% (p=0.000 n=20+18)
Testdata/StringEscaped/Marshal/Concrete 2.14GB/s ±10% 2.17GB/s ± 5% ~ (p=0.411 n=19+20)
Testdata/StringEscaped/Unmarshal/Concrete 282MB/s ± 6% 287MB/s ± 5% ~ (p=0.097 n=17+20)
Testdata/StringUnicode/Marshal/Concrete 937MB/s ± 6% 953MB/s ± 4% ~ (p=0.109 n=19+19)
Testdata/StringUnicode/Unmarshal/Concrete 803MB/s ± 5% 806MB/s ± 5% ~ (p=0.775 n=19+18)
Testdata/SyntheaFhir/Marshal/Concrete 374MB/s ±11% 392MB/s ± 5% +4.76% (p=0.000 n=20+20)
Testdata/SyntheaFhir/Unmarshal/Concrete 559MB/s ± 7% 584MB/s ± 9% +4.52% (p=0.001 n=20+20)
Testdata/TwitterStatus/Marshal/Concrete 1.04GB/s ±11% 1.04GB/s ±12% ~ (p=0.513 n=20+19)
Testdata/TwitterStatus/Unmarshal/Concrete 585MB/s ± 6% 602MB/s ±15% +2.86% (p=0.015 n=20+20)
name old allocs/op new allocs/op delta
Testdata/CanadaGeometry/Marshal/Concrete 1.00 ± 0% 1.00 ± 0% ~ (all equal)
Testdata/CanadaGeometry/Unmarshal/Concrete 1.63k ± 0% 1.63k ± 0% ~ (all equal)
Testdata/CitmCatalog/Marshal/Concrete 125 ± 0% 125 ± 0% ~ (all equal)
Testdata/CitmCatalog/Unmarshal/Concrete 7.28k ± 0% 7.28k ± 0% ~ (all equal)
Testdata/GolangSource/Marshal/Concrete 1.00 ± 0% 1.00 ± 0% ~ (all equal)
Testdata/GolangSource/Unmarshal/Concrete 13.9k ± 0% 13.9k ± 0% ~ (all equal)
Testdata/StringEscaped/Marshal/Concrete 1.00 ± 0% 1.00 ± 0% ~ (all equal)
Testdata/StringEscaped/Unmarshal/Concrete 168 ± 0% 168 ± 0% ~ (all equal)
Testdata/StringUnicode/Marshal/Concrete 1.00 ± 0% 1.00 ± 0% ~ (all equal)
Testdata/StringUnicode/Unmarshal/Concrete 50.0 ± 0% 50.0 ± 0% ~ (all equal)
Testdata/SyntheaFhir/Marshal/Concrete 2.56k ± 0% 2.56k ± 0% ~ (all equal)
Testdata/SyntheaFhir/Unmarshal/Concrete 14.6k ± 0% 14.6k ± 0% ~ (all equal)
Testdata/TwitterStatus/Marshal/Concrete 49.0 ± 0% 49.0 ± 0% ~ (all equal)
Testdata/TwitterStatus/Unmarshal/Concrete 2.98k ± 0% 2.98k ± 0% ~ (all equal)
Change-Id: I303b83302352930c283da803646407d16984abb0
Reviewed-on: https://go-review.googlesource.com/c/go/+/772920
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
cb39d7aa5c
commit
60a809d31a
14 changed files with 37 additions and 58 deletions
|
|
@ -254,7 +254,7 @@ func ConsumeStringResumable(flags *ValueFlags, b []byte, resumeOffset int, valid
|
|||
// Any invalid UTF-8 within the string will be replaced with utf8.RuneError,
|
||||
// but the error will be specified as having encountered such an error.
|
||||
// The input must be an entire JSON string with no surrounding whitespace.
|
||||
func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, err error) {
|
||||
func AppendUnquote(dst, src []byte) (v []byte, err error) {
|
||||
dst = slices.Grow(dst, len(src))
|
||||
|
||||
// Consume the leading double quote.
|
||||
|
|
@ -292,7 +292,7 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, er
|
|||
return dst, err
|
||||
}
|
||||
|
||||
switch r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:]))); {
|
||||
switch r, rn := utf8.DecodeRune(src[n:]); {
|
||||
// Handle UTF-8 encoded byte sequence.
|
||||
// Due to specialized handling of ASCII above, we know that
|
||||
// all normal sequences at this point must be 2 bytes or larger.
|
||||
|
|
@ -364,7 +364,7 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, er
|
|||
// Handle invalid UTF-8.
|
||||
case r == utf8.RuneError:
|
||||
dst = append(dst, src[i:n]...)
|
||||
if !utf8.FullRuneInString(string(truncateMaxUTF8(src[n:]))) {
|
||||
if !utf8.FullRune(src[n:]) {
|
||||
return dst, io.ErrUnexpectedEOF
|
||||
}
|
||||
// NOTE: An unescaped string may be longer than the escaped string
|
||||
|
|
@ -387,7 +387,7 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, er
|
|||
|
||||
// hasEscapedUTF16Prefix reports whether b is possibly
|
||||
// the truncated prefix of a \uFFFF escape sequence.
|
||||
func hasEscapedUTF16Prefix[Bytes ~[]byte | ~string](b Bytes, lowerSurrogateHalf bool) bool {
|
||||
func hasEscapedUTF16Prefix(b []byte, lowerSurrogateHalf bool) bool {
|
||||
for i := range len(b) {
|
||||
switch c := b[i]; {
|
||||
case i == 0 && c != '\\':
|
||||
|
|
@ -565,7 +565,7 @@ beforeExponent:
|
|||
// parseHexUint16 is similar to strconv.ParseUint,
|
||||
// but operates directly on []byte and is optimized for base-16.
|
||||
// See https://go.dev/issue/42429.
|
||||
func parseHexUint16[Bytes ~[]byte | ~string](b Bytes) (v uint16, ok bool) {
|
||||
func parseHexUint16(b []byte) (v uint16, ok bool) {
|
||||
if len(b) != 4 {
|
||||
return 0, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ func TestConsumeString(t *testing.T) {
|
|||
t.Errorf("consumeString(%q, false) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.wantUTF8, tt.wantErrUTF8)
|
||||
}
|
||||
|
||||
gotUnquote, gotErr := AppendUnquote(nil, tt.in)
|
||||
gotUnquote, gotErr := AppendUnquote(nil, []byte(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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ var escapeASCII = [...]uint8{
|
|||
// NeedEscape reports whether src needs escaping of any characters.
|
||||
// It conservatively assumes EscapeForHTML and EscapeForJS.
|
||||
// It reports true for inputs with invalid UTF-8.
|
||||
func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool {
|
||||
func NeedEscape(src []byte) bool {
|
||||
var i int
|
||||
for uint(len(src)) > uint(i) {
|
||||
if c := src[i]; c < utf8.RuneSelf {
|
||||
|
|
@ -41,7 +41,7 @@ func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool {
|
|||
}
|
||||
i++
|
||||
} else {
|
||||
r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[i:])))
|
||||
r, rn := utf8.DecodeRune(src[i:])
|
||||
if r == utf8.RuneError || r == '\u2028' || r == '\u2029' {
|
||||
return true
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool {
|
|||
// invalid bytes are replaced with the Unicode replacement character ('\ufffd').
|
||||
// If no escape flags are set, then the shortest representable form is used,
|
||||
// which is also the canonical form for strings (RFC 8785, section 3.2.2.2).
|
||||
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflags.Flags) ([]byte, error) {
|
||||
func AppendQuote(dst, src []byte, flags *jsonflags.Flags) ([]byte, error) {
|
||||
var i, n int
|
||||
var hasInvalidUTF8 bool
|
||||
dst = slices.Grow(dst, len(`"`)+len(src)+len(`"`))
|
||||
|
|
@ -82,7 +82,7 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflag
|
|||
}
|
||||
} else {
|
||||
// Handle multi-byte Unicode.
|
||||
r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:])))
|
||||
r, rn := utf8.DecodeRune(src[n:])
|
||||
n += rn
|
||||
if r != utf8.RuneError && r != '\u2028' && r != '\u2029' {
|
||||
continue // no escaping possibly needed
|
||||
|
|
@ -178,7 +178,7 @@ func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error
|
|||
}
|
||||
i++
|
||||
} else {
|
||||
r, rn := utf8.DecodeRune(truncateMaxUTF8(src[i:]))
|
||||
r, rn := utf8.DecodeRune(src[i:])
|
||||
if (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS) {
|
||||
dst = append(dst, src[lastAppendIndex:i]...)
|
||||
dst = appendEscapedUnicode(dst, r)
|
||||
|
|
|
|||
|
|
@ -68,12 +68,12 @@ func TestAppendQuote(t *testing.T) {
|
|||
flags.Set(tt.flags | 1)
|
||||
|
||||
flags.Set(jsonflags.AllowInvalidUTF8 | 1)
|
||||
got, gotErr := AppendQuote(nil, tt.in, &flags)
|
||||
got, gotErr := AppendQuote(nil, []byte(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); {
|
||||
switch got, gotErr := AppendQuote(nil, []byte(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)):
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package jsonwire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
|
@ -59,8 +60,8 @@ func TrimSuffixByte(b []byte, c byte) []byte {
|
|||
}
|
||||
|
||||
// QuoteRune quotes the first rune in the input.
|
||||
func QuoteRune[Bytes ~[]byte | ~string](b Bytes) string {
|
||||
r, n := utf8.DecodeRuneInString(string(truncateMaxUTF8(b)))
|
||||
func QuoteRune(b []byte) string {
|
||||
r, n := utf8.DecodeRune(b)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return `'\x` + strconv.FormatUint(uint64(b[0]), 16) + `'`
|
||||
}
|
||||
|
|
@ -70,7 +71,7 @@ func QuoteRune[Bytes ~[]byte | ~string](b Bytes) string {
|
|||
// CompareUTF16 lexicographically compares x to y according
|
||||
// to the UTF-16 codepoints of the UTF-8 encoded input strings.
|
||||
// This implements the ordering specified in RFC 8785, section 3.2.3.
|
||||
func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
|
||||
func CompareUTF16(x, y []byte) int {
|
||||
// NOTE: This is an optimized, mostly allocation-free implementation
|
||||
// of CompareUTF16Simple in wire_test.go. FuzzCompareUTF16 verifies that the
|
||||
// two implementations agree on the result of comparing any two strings.
|
||||
|
|
@ -93,8 +94,8 @@ func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
|
|||
}
|
||||
|
||||
// Decode next pair of runes as UTF-8.
|
||||
rx, nx := utf8.DecodeRuneInString(string(truncateMaxUTF8(x)))
|
||||
ry, ny := utf8.DecodeRuneInString(string(truncateMaxUTF8(y)))
|
||||
rx, nx := utf8.DecodeRune(x)
|
||||
ry, ny := utf8.DecodeRune(y)
|
||||
|
||||
selfx := isUTF16Self(rx)
|
||||
selfy := isUTF16Self(ry)
|
||||
|
|
@ -123,33 +124,11 @@ func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
|
|||
}
|
||||
}
|
||||
|
||||
// truncateMaxUTF8 truncates b such it contains at least one rune.
|
||||
//
|
||||
// The utf8 package currently lacks generic variants, which complicates
|
||||
// generic functions that operates on either []byte or string.
|
||||
// As a hack, we always call the utf8 function operating on strings,
|
||||
// but always truncate the input such that the result is identical.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// utf8.DecodeRuneInString(string(truncateMaxUTF8(b)))
|
||||
//
|
||||
// Converting a []byte to a string is stack allocated since
|
||||
// truncateMaxUTF8 guarantees that the []byte is short.
|
||||
func truncateMaxUTF8[Bytes ~[]byte | ~string](b Bytes) Bytes {
|
||||
// TODO(https://go.dev/issue/56948): Remove this function and
|
||||
// instead directly call generic utf8 functions wherever used.
|
||||
if len(b) > utf8.UTFMax {
|
||||
return b[:utf8.UTFMax]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// TODO(https://go.dev/issue/70547): Use utf8.ErrInvalid instead.
|
||||
var ErrInvalidUTF8 = errors.New("invalid UTF-8")
|
||||
|
||||
func NewInvalidCharacterError[Bytes ~[]byte | ~string](prefix Bytes, where string) error {
|
||||
what := QuoteRune(prefix)
|
||||
what := QuoteRune([]byte(prefix))
|
||||
return errors.New("invalid character " + what + " " + where)
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +137,7 @@ func NewInvalidEscapeSequenceError[Bytes ~[]byte | ~string](what Bytes) error {
|
|||
if len(what) > 6 {
|
||||
label = "surrogate pair"
|
||||
}
|
||||
needEscape := strings.IndexFunc(string(what), func(r rune) bool {
|
||||
needEscape := bytes.IndexFunc([]byte(what), func(r rune) bool {
|
||||
return r == '`' || r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r)
|
||||
}) >= 0
|
||||
if needEscape {
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
|
|||
if !isVerbatim {
|
||||
var err error
|
||||
b2 := append(e.availBuffer, b[pos+len(`"`):len(b)-len(`"`)]...)
|
||||
b, err = jsonwire.AppendQuote(b[:pos], string(b2), &e.Flags)
|
||||
b, err = jsonwire.AppendQuote(b[:pos], b2, &e.Flags)
|
||||
e.availBuffer = b2[:0]
|
||||
if err != nil {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func testEncoder(t *testing.T, where jsontest.CasePos, formatName, typeName stri
|
|||
default:
|
||||
val := Value(tok.String())
|
||||
if tok.Kind() == '"' {
|
||||
val, _ = jsonwire.AppendQuote(nil, tok.String(), &jsonflags.Flags{})
|
||||
val, _ = jsonwire.AppendQuote(nil, []byte(tok.String()), &jsonflags.Flags{})
|
||||
}
|
||||
if err := enc.WriteValue(val); err != nil {
|
||||
t.Fatalf("%s: Encoder.WriteValue error: %v", where, err)
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ func WithIndent(indent string) Options {
|
|||
|
||||
// Otherwise, allocate for this unique value.
|
||||
if s := strings.Trim(indent, " \t"); len(s) > 0 {
|
||||
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent")
|
||||
panic("json: invalid character " + jsonwire.QuoteRune([]byte(s)) + " in indent")
|
||||
}
|
||||
return jsonopts.Indent(indent)
|
||||
}
|
||||
|
|
@ -264,7 +264,7 @@ func WithIndent(indent string) Options {
|
|||
// Use of this option implies [Multiline] being set to true.
|
||||
func WithIndentPrefix(prefix string) Options {
|
||||
if s := strings.Trim(prefix, " \t"); len(s) > 0 {
|
||||
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent prefix")
|
||||
panic("json: invalid character " + jsonwire.QuoteRune([]byte(s)) + " in indent prefix")
|
||||
}
|
||||
return jsonopts.IndentPrefix(prefix)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
// and an error is returned at the end indicating the presence of invalid UTF-8.
|
||||
// The dst must not overlap with the src.
|
||||
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
|
||||
dst, err := jsonwire.AppendQuote(dst, src, &jsonflags.Flags{})
|
||||
dst, err := jsonwire.AppendQuote(dst, []byte(src), &jsonflags.Flags{})
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: err}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)
|
|||
// Any trailing bytes after the JSON string literal results in an error.
|
||||
// The dst must not overlap with the src.
|
||||
func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
|
||||
dst, err := jsonwire.AppendUnquote(dst, src)
|
||||
dst, err := jsonwire.AppendUnquote(dst, []byte(src))
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: err}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ func (p Pointer) LastToken() string {
|
|||
|
||||
// AppendToken appends a token to the end of p and returns the full pointer.
|
||||
func (p Pointer) AppendToken(tok string) Pointer {
|
||||
return Pointer(appendEscapePointerName([]byte(p+"/"), tok))
|
||||
return Pointer(appendEscapePointerName([]byte(p+"/"), []byte(tok)))
|
||||
}
|
||||
|
||||
// TODO: Add Pointer.AppendTokens,
|
||||
|
|
@ -202,7 +202,7 @@ func (s state) appendStackPointer(b []byte, where int) []byte {
|
|||
return b
|
||||
}
|
||||
|
||||
func appendEscapePointerName[Bytes ~[]byte | ~string](b []byte, name Bytes) []byte {
|
||||
func appendEscapePointerName(b, name []byte) []byte {
|
||||
for _, r := range string(name) {
|
||||
// Per RFC 6901, section 3, escape '~' and '/' characters.
|
||||
switch r {
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error)
|
|||
}
|
||||
} else if len(t.str) != 0 && t.num == 0 {
|
||||
// Handle exact string value.
|
||||
return jsonwire.AppendQuote(dst, t.str, flags)
|
||||
return jsonwire.AppendQuote(dst, []byte(t.str), flags)
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
|
|
@ -572,7 +572,7 @@ func (k Kind) String() string {
|
|||
case ']':
|
||||
return "]"
|
||||
default:
|
||||
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune(string(k)) + ">"
|
||||
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune([]byte{byte(k)}) + ">"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ func makeStringArshaler(t reflect.Type) *arshaler {
|
|||
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyBoolsAndStrings) && !xe.Tokens.Last.NeedObjectName() {
|
||||
b := xe.Buf
|
||||
b = xe.Tokens.MayAppendDelim(b, '"')
|
||||
b, err := jsonwire.AppendQuote(b, s, &mo.Flags)
|
||||
b, err := jsonwire.AppendQuote(b, []byte(s), &mo.Flags)
|
||||
if err == nil {
|
||||
xe.Buf = b
|
||||
xe.Tokens.Last.Increment()
|
||||
|
|
@ -222,7 +222,7 @@ func makeStringArshaler(t reflect.Type) *arshaler {
|
|||
}
|
||||
|
||||
if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) {
|
||||
b, err := jsonwire.AppendQuote(nil, s, &mo.Flags)
|
||||
b, err := jsonwire.AppendQuote(nil, []byte(s), &mo.Flags)
|
||||
if err != nil {
|
||||
return newMarshalErrorBefore(enc, t, &jsontext.SyntacticError{Err: err})
|
||||
}
|
||||
|
|
@ -1148,7 +1148,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
|
|||
if !f.nameNeedEscape {
|
||||
b = append(b, f.quotedName...)
|
||||
} else {
|
||||
b, _ = jsonwire.AppendQuote(b, f.name, &mo.Flags)
|
||||
b, _ = jsonwire.AppendQuote(b, []byte(f.name), &mo.Flags)
|
||||
}
|
||||
xe.Buf = b
|
||||
xe.Names.ReplaceLastQuotedOffset(n0)
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
|
|||
mk := newAddressableValue(m.Type().Key())
|
||||
mv := newAddressableValue(m.Type().Elem())
|
||||
marshalKey := func(mk addressableValue) error {
|
||||
b, err := jsonwire.AppendQuote(enc.AvailableBuffer(), mk.String(), &mo.Flags)
|
||||
b, err := jsonwire.AppendQuote(enc.AvailableBuffer(), []byte(mk.String()), &mo.Flags)
|
||||
if err != nil {
|
||||
return newMarshalErrorBefore(enc, m.Type().Key(), err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -453,9 +453,9 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
|
|||
}
|
||||
tag = tag[n:]
|
||||
}
|
||||
b, _ := jsonwire.AppendQuote(nil, out.name, &jsonflags.Flags{})
|
||||
b, _ := jsonwire.AppendQuote(nil, []byte(out.name), &jsonflags.Flags{})
|
||||
out.quotedName = string(b)
|
||||
out.nameNeedEscape = jsonwire.NeedEscape(out.name)
|
||||
out.nameNeedEscape = jsonwire.NeedEscape([]byte(out.name))
|
||||
|
||||
// Handle any additional tag options (if any).
|
||||
var wasFormat bool
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue