mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-07 12:56:47 +00:00
d3f8f2d412
Previous decoder decodes a JSON number into a float64, which lacks 64-bit integer precision. I attempted to retrofit it with storing the raw bytes and parsed out number parts, see golang.org/cl/164377. While that is possible, the encoding logic for Value is not symmetrical with the decoding logic and can be confusing since both utilizes the same Value struct. Joe and I decided that it would be better to rewrite the JSON encoder and decoder to be token-based instead, removing the need for sharing a model type plus making it more efficient. Change-Id: Ic0601428a824be4e20141623409ab4d92b6167c7 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/165677 Reviewed-by: Damien Neil <dneil@google.com>
411 lines
8.0 KiB
Go
411 lines
8.0 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/golang/protobuf/v2/internal/encoding/json"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
)
|
|
|
|
// 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`,
|
|
wantOutIndent: `null`,
|
|
},
|
|
{
|
|
desc: "true",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteBool(true)
|
|
},
|
|
wantOut: `true`,
|
|
wantOutIndent: `true`,
|
|
},
|
|
{
|
|
desc: "false",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteBool(false)
|
|
},
|
|
wantOut: `false`,
|
|
wantOutIndent: `false`,
|
|
},
|
|
{
|
|
desc: "string",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteString("hello world")
|
|
},
|
|
wantOut: `"hello world"`,
|
|
wantOutIndent: `"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`,
|
|
wantOutIndent: `1.0199999809265137`,
|
|
},
|
|
{
|
|
desc: "float64 max value",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(math.MaxFloat64, 64)
|
|
},
|
|
wantOut: `1.7976931348623157e+308`,
|
|
wantOutIndent: `1.7976931348623157e+308`,
|
|
},
|
|
{
|
|
desc: "float64 min value",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(-math.MaxFloat64, 64)
|
|
},
|
|
wantOut: `-1.7976931348623157e+308`,
|
|
wantOutIndent: `-1.7976931348623157e+308`,
|
|
},
|
|
{
|
|
desc: "float64 NaN",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(math.NaN(), 64)
|
|
},
|
|
wantOut: `"NaN"`,
|
|
wantOutIndent: `"NaN"`,
|
|
},
|
|
{
|
|
desc: "float64 Infinity",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(math.Inf(+1), 64)
|
|
},
|
|
wantOut: `"Infinity"`,
|
|
wantOutIndent: `"Infinity"`,
|
|
},
|
|
{
|
|
desc: "float64 -Infinity",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(math.Inf(-1), 64)
|
|
},
|
|
wantOut: `"-Infinity"`,
|
|
wantOutIndent: `"-Infinity"`,
|
|
},
|
|
{
|
|
desc: "float32",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(1.02, 32)
|
|
},
|
|
wantOut: `1.02`,
|
|
wantOutIndent: `1.02`,
|
|
},
|
|
{
|
|
desc: "float32 max value",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(math.MaxFloat32, 32)
|
|
},
|
|
wantOut: `3.4028235e+38`,
|
|
wantOutIndent: `3.4028235e+38`,
|
|
},
|
|
{
|
|
desc: "float32 min value",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteFloat(-math.MaxFloat32, 32)
|
|
},
|
|
wantOut: `-3.4028235e+38`,
|
|
wantOutIndent: `-3.4028235e+38`,
|
|
},
|
|
{
|
|
desc: "int",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteInt(-math.MaxInt64)
|
|
},
|
|
wantOut: `-9223372036854775807`,
|
|
wantOutIndent: `-9223372036854775807`,
|
|
},
|
|
{
|
|
desc: "uint",
|
|
write: func(e *json.Encoder) {
|
|
e.WriteUint(math.MaxUint64)
|
|
},
|
|
wantOut: `18446744073709551615`,
|
|
wantOutIndent: `18446744073709551615`,
|
|
},
|
|
{
|
|
desc: "empty object",
|
|
write: func(e *json.Encoder) {
|
|
e.StartObject()
|
|
e.EndObject()
|
|
},
|
|
wantOut: `{}`,
|
|
wantOutIndent: `{}`,
|
|
},
|
|
{
|
|
desc: "empty array",
|
|
write: func(e *json.Encoder) {
|
|
e.StartArray()
|
|
e.EndArray()
|
|
},
|
|
wantOut: `[]`,
|
|
wantOutIndent: `[]`,
|
|
},
|
|
{
|
|
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
|
|
]
|
|
]
|
|
}`,
|
|
},
|
|
{
|
|
desc: "string contains rune error",
|
|
write: func(e *json.Encoder) {
|
|
// WriteString returns non-fatal error for invalid UTF sequence, but
|
|
// should still output the written value. See TestWriteStringError
|
|
// below that checks for this.
|
|
e.StartObject()
|
|
e.WriteName("invalid rune")
|
|
e.WriteString("abc\xff")
|
|
e.EndObject()
|
|
},
|
|
wantOut: "{\"invalid rune\":\"abc\xff\"}",
|
|
}}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
if tc.wantOut != "" {
|
|
enc, err := json.NewEncoder("")
|
|
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("\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("")
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|