// 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("") 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:\n%v\n\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:\n%v\n\n%v\n\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) } }) } }