Edward McFarlane 05cbe34333 encoding: add MarshalAppend to protojson and prototext
Adds MarshalAppend methods to allow for byte slices to be reused.
Copies signature from the binary encoding.

Small changes to internal json and text libraries to use strconv
AppendInt and AppendUint for number encoding.

Change-Id: Ife7c8979c1c153a0a0bf9b70b296b8158d38dffc
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/489615
Reviewed-by: Edward McFarlane <emcfarlane000@gmail.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Lasse Folger <lassefolger@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
2023-05-01 15:10:15 +00:00

400 lines
7.4 KiB
Go

// Copyright 2019 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.
package json_test
import (
"math"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/internal/detrand"
"google.golang.org/protobuf/internal/encoding/json"
)
// Disable detrand to enable direct comparisons on outputs.
func init() { detrand.Disable() }
// splitLines is a cmpopts.Option for comparing strings with line breaks.
var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string {
return strings.Split(s, "\n")
})
func TestEncoder(t *testing.T) {
tests := []struct {
desc string
write func(*json.Encoder)
wantOut string
wantOutIndent string
}{
{
desc: "null",
write: func(e *json.Encoder) {
e.WriteNull()
},
wantOut: `null`,
},
{
desc: "true",
write: func(e *json.Encoder) {
e.WriteBool(true)
},
wantOut: `true`,
},
{
desc: "false",
write: func(e *json.Encoder) {
e.WriteBool(false)
},
wantOut: `false`,
},
{
desc: "string",
write: func(e *json.Encoder) {
e.WriteString("hello world")
},
wantOut: `"hello world"`,
},
{
desc: "string contains escaped characters",
write: func(e *json.Encoder) {
e.WriteString("\u0000\"\\/\b\f\n\r\t")
},
wantOut: `"\u0000\"\\/\b\f\n\r\t"`,
},
{
desc: "float64",
write: func(e *json.Encoder) {
e.WriteFloat(1.0199999809265137, 64)
},
wantOut: `1.0199999809265137`,
},
{
desc: "float64 max value",
write: func(e *json.Encoder) {
e.WriteFloat(math.MaxFloat64, 64)
},
wantOut: `1.7976931348623157e+308`,
},
{
desc: "float64 min value",
write: func(e *json.Encoder) {
e.WriteFloat(-math.MaxFloat64, 64)
},
wantOut: `-1.7976931348623157e+308`,
},
{
desc: "float64 NaN",
write: func(e *json.Encoder) {
e.WriteFloat(math.NaN(), 64)
},
wantOut: `"NaN"`,
},
{
desc: "float64 Infinity",
write: func(e *json.Encoder) {
e.WriteFloat(math.Inf(+1), 64)
},
wantOut: `"Infinity"`,
},
{
desc: "float64 -Infinity",
write: func(e *json.Encoder) {
e.WriteFloat(math.Inf(-1), 64)
},
wantOut: `"-Infinity"`,
},
{
desc: "float64 negative zero",
write: func(e *json.Encoder) {
e.WriteFloat(math.Copysign(0, -1), 64)
},
wantOut: `-0`,
},
{
desc: "float32",
write: func(e *json.Encoder) {
e.WriteFloat(1.02, 32)
},
wantOut: `1.02`,
},
{
desc: "float32 max value",
write: func(e *json.Encoder) {
e.WriteFloat(math.MaxFloat32, 32)
},
wantOut: `3.4028235e+38`,
},
{
desc: "float32 min value",
write: func(e *json.Encoder) {
e.WriteFloat(-math.MaxFloat32, 32)
},
wantOut: `-3.4028235e+38`,
},
{
desc: "float32 negative zero",
write: func(e *json.Encoder) {
e.WriteFloat(math.Copysign(0, -1), 32)
},
wantOut: `-0`,
},
{
desc: "int",
write: func(e *json.Encoder) {
e.WriteInt(-math.MaxInt64)
},
wantOut: `-9223372036854775807`,
},
{
desc: "uint",
write: func(e *json.Encoder) {
e.WriteUint(math.MaxUint64)
},
wantOut: `18446744073709551615`,
},
{
desc: "empty object",
write: func(e *json.Encoder) {
e.StartObject()
e.EndObject()
},
wantOut: `{}`,
},
{
desc: "empty array",
write: func(e *json.Encoder) {
e.StartArray()
e.EndArray()
},
wantOut: `[]`,
},
{
desc: "object with one member",
write: func(e *json.Encoder) {
e.StartObject()
e.WriteName("hello")
e.WriteString("world")
e.EndObject()
},
wantOut: `{"hello":"world"}`,
wantOutIndent: `{
"hello": "world"
}`,
},
{
desc: "array with one member",
write: func(e *json.Encoder) {
e.StartArray()
e.WriteNull()
e.EndArray()
},
wantOut: `[null]`,
wantOutIndent: `[
null
]`,
},
{
desc: "simple object",
write: func(e *json.Encoder) {
e.StartObject()
{
e.WriteName("null")
e.WriteNull()
}
{
e.WriteName("bool")
e.WriteBool(true)
}
{
e.WriteName("string")
e.WriteString("hello")
}
{
e.WriteName("float")
e.WriteFloat(6.28318, 64)
}
{
e.WriteName("int")
e.WriteInt(42)
}
{
e.WriteName("uint")
e.WriteUint(47)
}
e.EndObject()
},
wantOut: `{"null":null,"bool":true,"string":"hello","float":6.28318,"int":42,"uint":47}`,
wantOutIndent: `{
"null": null,
"bool": true,
"string": "hello",
"float": 6.28318,
"int": 42,
"uint": 47
}`,
},
{
desc: "simple array",
write: func(e *json.Encoder) {
e.StartArray()
{
e.WriteString("hello")
e.WriteFloat(6.28318, 32)
e.WriteInt(42)
e.WriteUint(47)
e.WriteBool(true)
e.WriteNull()
}
e.EndArray()
},
wantOut: `["hello",6.28318,42,47,true,null]`,
wantOutIndent: `[
"hello",
6.28318,
42,
47,
true,
null
]`,
},
{
desc: "fancy object",
write: func(e *json.Encoder) {
e.StartObject()
{
e.WriteName("object0")
e.StartObject()
e.EndObject()
}
{
e.WriteName("array0")
e.StartArray()
e.EndArray()
}
{
e.WriteName("object1")
e.StartObject()
{
e.WriteName("null")
e.WriteNull()
}
{
e.WriteName("object1-1")
e.StartObject()
{
e.WriteName("bool")
e.WriteBool(false)
}
{
e.WriteName("float")
e.WriteFloat(3.14159, 32)
}
e.EndObject()
}
e.EndObject()
}
{
e.WriteName("array1")
e.StartArray()
{
e.WriteNull()
e.StartObject()
e.EndObject()
e.StartObject()
{
e.WriteName("hello")
e.WriteString("world")
}
{
e.WriteName("hola")
e.WriteString("mundo")
}
e.EndObject()
e.StartArray()
{
e.WriteUint(1)
e.WriteUint(0)
e.WriteUint(1)
}
e.EndArray()
}
e.EndArray()
}
e.EndObject()
},
wantOutIndent: `{
"object0": {},
"array0": [],
"object1": {
"null": null,
"object1-1": {
"bool": false,
"float": 3.14159
}
},
"array1": [
null,
{},
{
"hello": "world",
"hola": "mundo"
},
[
1,
0,
1
]
]
}`,
}}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
if tc.wantOut != "" {
enc, err := json.NewEncoder(nil, "")
if err != nil {
t.Fatalf("NewEncoder() returned error: %v", err)
}
tc.write(enc)
got := string(enc.Bytes())
if got != tc.wantOut {
t.Errorf("%s:\n<got>:\n%v\n<want>\n%v\n", tc.desc, got, tc.wantOut)
}
}
if tc.wantOutIndent != "" {
enc, err := json.NewEncoder(nil, "\t")
if err != nil {
t.Fatalf("NewEncoder() returned error: %v", err)
}
tc.write(enc)
got, want := string(enc.Bytes()), tc.wantOutIndent
if got != want {
t.Errorf("%s(indent):\n<got>:\n%v\n<want>\n%v\n<diff -want +got>\n%v\n",
tc.desc, got, want, cmp.Diff(want, got, splitLines))
}
}
})
}
}
func TestWriteStringError(t *testing.T) {
tests := []string{"abc\xff"}
for _, in := range tests {
t.Run(in, func(t *testing.T) {
enc, err := json.NewEncoder(nil, "")
if err != nil {
t.Fatalf("NewEncoder() returned error: %v", err)
}
if err := enc.WriteString(in); err == nil {
t.Errorf("WriteString(%v): got nil error, want error", in)
}
})
}
}