blob: 461fc6b362ad5778314b246d18ac528c0ed3eec0 [file] [log] [blame]
Herbie Ong7b828bc2019-02-08 19:56:24 -08001// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package jsonpb_test
6
7import (
8 "math"
9 "strings"
10 "testing"
11
12 "github.com/golang/protobuf/v2/encoding/jsonpb"
13 "github.com/golang/protobuf/v2/internal/encoding/pack"
14 "github.com/golang/protobuf/v2/internal/scalar"
15 "github.com/golang/protobuf/v2/proto"
16 "github.com/google/go-cmp/cmp"
17 "github.com/google/go-cmp/cmp/cmpopts"
18
Herbie Ong7b828bc2019-02-08 19:56:24 -080019 "github.com/golang/protobuf/v2/encoding/testprotos/pb2"
20 "github.com/golang/protobuf/v2/encoding/testprotos/pb3"
21)
22
23// splitLines is a cmpopts.Option for comparing strings with line breaks.
24var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string {
25 return strings.Split(s, "\n")
26})
27
28func pb2Enum(i int32) *pb2.Enum {
29 p := new(pb2.Enum)
30 *p = pb2.Enum(i)
31 return p
32}
33
34func pb2Enums_NestedEnum(i int32) *pb2.Enums_NestedEnum {
35 p := new(pb2.Enums_NestedEnum)
36 *p = pb2.Enums_NestedEnum(i)
37 return p
38}
39
40func TestMarshal(t *testing.T) {
41 tests := []struct {
42 desc string
43 mo jsonpb.MarshalOptions
44 input proto.Message
45 want string
46 }{{
47 desc: "proto2 optional scalars not set",
48 input: &pb2.Scalars{},
49 want: "{}",
50 }, {
51 desc: "proto3 scalars not set",
52 input: &pb3.Scalars{},
53 want: "{}",
54 }, {
55 desc: "proto2 optional scalars set to zero values",
56 input: &pb2.Scalars{
57 OptBool: scalar.Bool(false),
58 OptInt32: scalar.Int32(0),
59 OptInt64: scalar.Int64(0),
60 OptUint32: scalar.Uint32(0),
61 OptUint64: scalar.Uint64(0),
62 OptSint32: scalar.Int32(0),
63 OptSint64: scalar.Int64(0),
64 OptFixed32: scalar.Uint32(0),
65 OptFixed64: scalar.Uint64(0),
66 OptSfixed32: scalar.Int32(0),
67 OptSfixed64: scalar.Int64(0),
68 OptFloat: scalar.Float32(0),
69 OptDouble: scalar.Float64(0),
70 OptBytes: []byte{},
71 OptString: scalar.String(""),
72 },
73 want: `{
74 "optBool": false,
75 "optInt32": 0,
76 "optInt64": "0",
77 "optUint32": 0,
78 "optUint64": "0",
79 "optSint32": 0,
80 "optSint64": "0",
81 "optFixed32": 0,
82 "optFixed64": "0",
83 "optSfixed32": 0,
84 "optSfixed64": "0",
85 "optFloat": 0,
86 "optDouble": 0,
87 "optBytes": "",
88 "optString": ""
89}`,
90 }, {
91 desc: "proto2 optional scalars set to some values",
92 input: &pb2.Scalars{
93 OptBool: scalar.Bool(true),
94 OptInt32: scalar.Int32(0xff),
95 OptInt64: scalar.Int64(0xdeadbeef),
96 OptUint32: scalar.Uint32(47),
97 OptUint64: scalar.Uint64(0xdeadbeef),
98 OptSint32: scalar.Int32(-1001),
99 OptSint64: scalar.Int64(-0xffff),
100 OptFixed64: scalar.Uint64(64),
101 OptSfixed32: scalar.Int32(-32),
102 OptFloat: scalar.Float32(1.02),
103 OptDouble: scalar.Float64(1.234),
104 OptBytes: []byte("\xe8\xb0\xb7\xe6\xad\x8c"),
105 OptString: scalar.String("谷歌"),
106 },
107 want: `{
108 "optBool": true,
109 "optInt32": 255,
110 "optInt64": "3735928559",
111 "optUint32": 47,
112 "optUint64": "3735928559",
113 "optSint32": -1001,
114 "optSint64": "-65535",
115 "optFixed64": "64",
116 "optSfixed32": -32,
117 "optFloat": 1.02,
118 "optDouble": 1.234,
119 "optBytes": "6LC35q2M",
120 "optString": "谷歌"
121}`,
122 }, {
123 desc: "float nan",
124 input: &pb3.Scalars{
125 SFloat: float32(math.NaN()),
126 },
127 want: `{
128 "sFloat": "NaN"
129}`,
130 }, {
131 desc: "float positive infinity",
132 input: &pb3.Scalars{
133 SFloat: float32(math.Inf(1)),
134 },
135 want: `{
136 "sFloat": "Infinity"
137}`,
138 }, {
139 desc: "float negative infinity",
140 input: &pb3.Scalars{
141 SFloat: float32(math.Inf(-1)),
142 },
143 want: `{
144 "sFloat": "-Infinity"
145}`,
146 }, {
147 desc: "double nan",
148 input: &pb3.Scalars{
149 SDouble: math.NaN(),
150 },
151 want: `{
152 "sDouble": "NaN"
153}`,
154 }, {
155 desc: "double positive infinity",
156 input: &pb3.Scalars{
157 SDouble: math.Inf(1),
158 },
159 want: `{
160 "sDouble": "Infinity"
161}`,
162 }, {
163 desc: "double negative infinity",
164 input: &pb3.Scalars{
165 SDouble: math.Inf(-1),
166 },
167 want: `{
168 "sDouble": "-Infinity"
169}`,
170 }, {
171 desc: "proto2 enum not set",
172 input: &pb2.Enums{},
173 want: "{}",
174 }, {
175 desc: "proto2 enum set to zero value",
176 input: &pb2.Enums{
177 OptEnum: pb2Enum(0),
178 OptNestedEnum: pb2Enums_NestedEnum(0),
179 },
180 want: `{
181 "optEnum": 0,
182 "optNestedEnum": 0
183}`,
184 }, {
185 desc: "proto2 enum",
186 input: &pb2.Enums{
187 OptEnum: pb2.Enum_ONE.Enum(),
188 OptNestedEnum: pb2.Enums_UNO.Enum(),
189 },
190 want: `{
191 "optEnum": "ONE",
192 "optNestedEnum": "UNO"
193}`,
194 }, {
195 desc: "proto2 enum set to numeric values",
196 input: &pb2.Enums{
197 OptEnum: pb2Enum(2),
198 OptNestedEnum: pb2Enums_NestedEnum(2),
199 },
200 want: `{
201 "optEnum": "TWO",
202 "optNestedEnum": "DOS"
203}`,
204 }, {
205 desc: "proto2 enum set to unnamed numeric values",
206 input: &pb2.Enums{
207 OptEnum: pb2Enum(101),
208 OptNestedEnum: pb2Enums_NestedEnum(-101),
209 },
210 want: `{
211 "optEnum": 101,
212 "optNestedEnum": -101
213}`,
214 }, {
215 desc: "proto3 enum not set",
216 input: &pb3.Enums{},
217 want: "{}",
218 }, {
219 desc: "proto3 enum set to zero value",
220 input: &pb3.Enums{
221 SEnum: pb3.Enum_ZERO,
222 SNestedEnum: pb3.Enums_CERO,
223 },
224 want: "{}",
225 }, {
226 desc: "proto3 enum",
227 input: &pb3.Enums{
228 SEnum: pb3.Enum_ONE,
229 SNestedEnum: pb3.Enums_UNO,
230 },
231 want: `{
232 "sEnum": "ONE",
233 "sNestedEnum": "UNO"
234}`,
235 }, {
236 desc: "proto3 enum set to numeric values",
237 input: &pb3.Enums{
238 SEnum: 2,
239 SNestedEnum: 2,
240 },
241 want: `{
242 "sEnum": "TWO",
243 "sNestedEnum": "DOS"
244}`,
245 }, {
246 desc: "proto3 enum set to unnamed numeric values",
247 input: &pb3.Enums{
248 SEnum: -47,
249 SNestedEnum: 47,
250 },
251 want: `{
252 "sEnum": -47,
253 "sNestedEnum": 47
254}`,
255 }, {
256 desc: "proto2 nested message not set",
257 input: &pb2.Nests{},
258 want: "{}",
259 }, {
260 desc: "proto2 nested message set to empty",
261 input: &pb2.Nests{
262 OptNested: &pb2.Nested{},
263 Optgroup: &pb2.Nests_OptGroup{},
264 },
265 want: `{
266 "optNested": {},
267 "optgroup": {}
268}`,
269 }, {
270 desc: "proto2 nested messages",
271 input: &pb2.Nests{
272 OptNested: &pb2.Nested{
273 OptString: scalar.String("nested message"),
274 OptNested: &pb2.Nested{
275 OptString: scalar.String("another nested message"),
276 },
277 },
278 },
279 want: `{
280 "optNested": {
281 "optString": "nested message",
282 "optNested": {
283 "optString": "another nested message"
284 }
285 }
286}`,
287 }, {
288 desc: "proto2 groups",
289 input: &pb2.Nests{
290 Optgroup: &pb2.Nests_OptGroup{
291 OptString: scalar.String("inside a group"),
292 OptNested: &pb2.Nested{
293 OptString: scalar.String("nested message inside a group"),
294 },
295 Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{
296 OptFixed32: scalar.Uint32(47),
297 },
298 },
299 },
300 want: `{
301 "optgroup": {
302 "optString": "inside a group",
303 "optNested": {
304 "optString": "nested message inside a group"
305 },
306 "optnestedgroup": {
307 "optFixed32": 47
308 }
309 }
310}`,
311 }, {
312 desc: "proto3 nested message not set",
313 input: &pb3.Nests{},
314 want: "{}",
315 }, {
316 desc: "proto3 nested message set to empty",
317 input: &pb3.Nests{
318 SNested: &pb3.Nested{},
319 },
320 want: `{
321 "sNested": {}
322}`,
323 }, {
324 desc: "proto3 nested message",
325 input: &pb3.Nests{
326 SNested: &pb3.Nested{
327 SString: "nested message",
328 SNested: &pb3.Nested{
329 SString: "another nested message",
330 },
331 },
332 },
333 want: `{
334 "sNested": {
335 "sString": "nested message",
336 "sNested": {
337 "sString": "another nested message"
338 }
339 }
340}`,
341 }, {
342 desc: "oneof not set",
343 input: &pb3.Oneofs{},
344 want: "{}",
345 }, {
346 desc: "oneof set to empty string",
347 input: &pb3.Oneofs{
348 Union: &pb3.Oneofs_OneofString{},
349 },
350 want: `{
351 "oneofString": ""
352}`,
353 }, {
354 desc: "oneof set to string",
355 input: &pb3.Oneofs{
356 Union: &pb3.Oneofs_OneofString{
357 OneofString: "hello",
358 },
359 },
360 want: `{
361 "oneofString": "hello"
362}`,
363 }, {
364 desc: "oneof set to enum",
365 input: &pb3.Oneofs{
366 Union: &pb3.Oneofs_OneofEnum{
367 OneofEnum: pb3.Enum_ZERO,
368 },
369 },
370 want: `{
371 "oneofEnum": "ZERO"
372}`,
373 }, {
374 desc: "oneof set to empty message",
375 input: &pb3.Oneofs{
376 Union: &pb3.Oneofs_OneofNested{
377 OneofNested: &pb3.Nested{},
378 },
379 },
380 want: `{
381 "oneofNested": {}
382}`,
383 }, {
384 desc: "oneof set to message",
385 input: &pb3.Oneofs{
386 Union: &pb3.Oneofs_OneofNested{
387 OneofNested: &pb3.Nested{
388 SString: "nested message",
389 },
390 },
391 },
392 want: `{
393 "oneofNested": {
394 "sString": "nested message"
395 }
396}`,
397 }, {
398 desc: "repeated fields not set",
399 input: &pb2.Repeats{},
400 want: "{}",
401 }, {
402 desc: "repeated fields set to empty slices",
403 input: &pb2.Repeats{
404 RptBool: []bool{},
405 RptInt32: []int32{},
406 RptInt64: []int64{},
407 RptUint32: []uint32{},
408 RptUint64: []uint64{},
409 RptFloat: []float32{},
410 RptDouble: []float64{},
411 RptBytes: [][]byte{},
412 },
413 want: "{}",
414 }, {
415 desc: "repeated fields set to some values",
416 input: &pb2.Repeats{
417 RptBool: []bool{true, false, true, true},
418 RptInt32: []int32{1, 6, 0, 0},
419 RptInt64: []int64{-64, 47},
420 RptUint32: []uint32{0xff, 0xffff},
421 RptUint64: []uint64{0xdeadbeef},
422 RptFloat: []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034},
423 RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308},
424 RptString: []string{"hello", "世界"},
425 RptBytes: [][]byte{
426 []byte("hello"),
427 []byte("\xe4\xb8\x96\xe7\x95\x8c"),
428 },
429 },
430 want: `{
431 "rptBool": [
432 true,
433 false,
434 true,
435 true
436 ],
437 "rptInt32": [
438 1,
439 6,
440 0,
441 0
442 ],
443 "rptInt64": [
444 "-64",
445 "47"
446 ],
447 "rptUint32": [
448 255,
449 65535
450 ],
451 "rptUint64": [
452 "3735928559"
453 ],
454 "rptFloat": [
455 "NaN",
456 "Infinity",
457 "-Infinity",
458 1.034
459 ],
460 "rptDouble": [
461 "NaN",
462 "Infinity",
463 "-Infinity",
464 1.23e-308
465 ],
466 "rptString": [
467 "hello",
468 "世界"
469 ],
470 "rptBytes": [
471 "aGVsbG8=",
472 "5LiW55WM"
473 ]
474}`,
475 }, {
476 desc: "repeated enums",
477 input: &pb2.Enums{
478 RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
479 RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10},
480 },
481 want: `{
482 "rptEnum": [
483 "ONE",
484 "TWO",
485 "TEN",
486 42
487 ],
488 "rptNestedEnum": [
489 "DOS",
490 47,
491 "DIEZ"
492 ]
493}`,
494 }, {
495 desc: "repeated messages set to empty",
496 input: &pb2.Nests{
497 RptNested: []*pb2.Nested{},
498 Rptgroup: []*pb2.Nests_RptGroup{},
499 },
500 want: "{}",
501 }, {
502 desc: "repeated messages",
503 input: &pb2.Nests{
504 RptNested: []*pb2.Nested{
505 {
506 OptString: scalar.String("repeat nested one"),
507 },
508 {
509 OptString: scalar.String("repeat nested two"),
510 OptNested: &pb2.Nested{
511 OptString: scalar.String("inside repeat nested two"),
512 },
513 },
514 {},
515 },
516 },
517 want: `{
518 "rptNested": [
519 {
520 "optString": "repeat nested one"
521 },
522 {
523 "optString": "repeat nested two",
524 "optNested": {
525 "optString": "inside repeat nested two"
526 }
527 },
528 {}
529 ]
530}`,
531 }, {
532 desc: "repeated messages contains nil value",
533 input: &pb2.Nests{
534 RptNested: []*pb2.Nested{nil, {}},
535 },
536 want: `{
537 "rptNested": [
538 {},
539 {}
540 ]
541}`,
542 }, {
543 desc: "repeated groups",
544 input: &pb2.Nests{
545 Rptgroup: []*pb2.Nests_RptGroup{
546 {
547 RptString: []string{"hello", "world"},
548 },
549 {},
550 nil,
551 },
552 },
553 want: `{
554 "rptgroup": [
555 {
556 "rptString": [
557 "hello",
558 "world"
559 ]
560 },
561 {},
562 {}
563 ]
564}`,
565 }, {
566 desc: "map fields not set",
567 input: &pb3.Maps{},
568 want: "{}",
569 }, {
570 desc: "map fields set to empty",
571 input: &pb3.Maps{
572 Int32ToStr: map[int32]string{},
573 BoolToUint32: map[bool]uint32{},
574 Uint64ToEnum: map[uint64]pb3.Enum{},
575 StrToNested: map[string]*pb3.Nested{},
576 StrToOneofs: map[string]*pb3.Oneofs{},
577 },
578 want: "{}",
579 }, {
580 desc: "map fields 1",
581 input: &pb3.Maps{
582 BoolToUint32: map[bool]uint32{
583 true: 42,
584 false: 101,
585 },
586 },
587 want: `{
588 "boolToUint32": {
589 "false": 101,
590 "true": 42
591 }
592}`,
593 }, {
594 desc: "map fields 2",
595 input: &pb3.Maps{
596 Int32ToStr: map[int32]string{
597 -101: "-101",
598 0xff: "0xff",
599 0: "zero",
600 },
601 },
602 want: `{
603 "int32ToStr": {
604 "-101": "-101",
605 "0": "zero",
606 "255": "0xff"
607 }
608}`,
609 }, {
610 desc: "map fields 3",
611 input: &pb3.Maps{
612 Uint64ToEnum: map[uint64]pb3.Enum{
613 1: pb3.Enum_ONE,
614 2: pb3.Enum_TWO,
615 10: pb3.Enum_TEN,
616 47: 47,
617 },
618 },
619 want: `{
620 "uint64ToEnum": {
621 "1": "ONE",
622 "2": "TWO",
623 "10": "TEN",
624 "47": 47
625 }
626}`,
627 }, {
628 desc: "map fields 4",
629 input: &pb3.Maps{
630 StrToNested: map[string]*pb3.Nested{
631 "nested": &pb3.Nested{
632 SString: "nested in a map",
633 },
634 },
635 },
636 want: `{
637 "strToNested": {
638 "nested": {
639 "sString": "nested in a map"
640 }
641 }
642}`,
643 }, {
644 desc: "map fields 5",
645 input: &pb3.Maps{
646 StrToOneofs: map[string]*pb3.Oneofs{
647 "string": &pb3.Oneofs{
648 Union: &pb3.Oneofs_OneofString{
649 OneofString: "hello",
650 },
651 },
652 "nested": &pb3.Oneofs{
653 Union: &pb3.Oneofs_OneofNested{
654 OneofNested: &pb3.Nested{
655 SString: "nested oneof in map field value",
656 },
657 },
658 },
659 },
660 },
661 want: `{
662 "strToOneofs": {
663 "nested": {
664 "oneofNested": {
665 "sString": "nested oneof in map field value"
666 }
667 },
668 "string": {
669 "oneofString": "hello"
670 }
671 }
672}`,
673 }, {
674 desc: "map field contains nil value",
675 input: &pb3.Maps{
676 StrToNested: map[string]*pb3.Nested{
677 "nil": nil,
678 },
679 },
680 want: `{
681 "strToNested": {
682 "nil": {}
683 }
684}`,
685 }, {
686 desc: "unknown fields are ignored",
687 input: &pb2.Scalars{
688 OptString: scalar.String("no unknowns"),
689 XXX_unrecognized: pack.Message{
690 pack.Tag{101, pack.BytesType}, pack.String("hello world"),
691 }.Marshal(),
692 },
693 want: `{
694 "optString": "no unknowns"
695}`,
696 }, {
697 desc: "json_name",
698 input: &pb3.JSONNames{
699 SString: "json_name",
700 },
701 want: `{
702 "foo_bar": "json_name"
703}`,
704 }}
705
706 for _, tt := range tests {
707 tt := tt
708 t.Run(tt.desc, func(t *testing.T) {
709 t.Parallel()
710 b, err := tt.mo.Marshal(tt.input)
711 if err != nil {
712 t.Errorf("Marshal() returned error: %v\n", err)
713 }
714 got := string(b)
715 if got != tt.want {
716 t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want)
717 if diff := cmp.Diff(tt.want, got, splitLines); diff != "" {
718 t.Errorf("Marshal() diff -want +got\n%v\n", diff)
719 }
720 }
721 })
722 }
723}