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