1// 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 protojson_test 6 7import ( 8 "bytes" 9 "math" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 14 "google.golang.org/protobuf/encoding/protojson" 15 "google.golang.org/protobuf/internal/detrand" 16 "google.golang.org/protobuf/internal/flags" 17 "google.golang.org/protobuf/proto" 18 "google.golang.org/protobuf/reflect/protoregistry" 19 "google.golang.org/protobuf/testing/protopack" 20 21 pb2 "google.golang.org/protobuf/internal/testprotos/textpb2" 22 pb3 "google.golang.org/protobuf/internal/testprotos/textpb3" 23 "google.golang.org/protobuf/types/known/anypb" 24 "google.golang.org/protobuf/types/known/durationpb" 25 "google.golang.org/protobuf/types/known/emptypb" 26 "google.golang.org/protobuf/types/known/fieldmaskpb" 27 "google.golang.org/protobuf/types/known/structpb" 28 "google.golang.org/protobuf/types/known/timestamppb" 29 "google.golang.org/protobuf/types/known/wrapperspb" 30) 31 32// Disable detrand to enable direct comparisons on outputs. 33func init() { detrand.Disable() } 34 35func TestMarshal(t *testing.T) { 36 tests := []struct { 37 desc string 38 mo protojson.MarshalOptions 39 input proto.Message 40 want string 41 wantErr bool // TODO: Verify error message substring. 42 skip bool 43 }{{ 44 desc: "proto2 optional scalars not set", 45 input: &pb2.Scalars{}, 46 want: "{}", 47 }, { 48 desc: "proto3 scalars not set", 49 input: &pb3.Scalars{}, 50 want: "{}", 51 }, { 52 desc: "proto3 optional not set", 53 input: &pb3.Proto3Optional{}, 54 want: "{}", 55 }, { 56 desc: "proto2 optional scalars set to zero values", 57 input: &pb2.Scalars{ 58 OptBool: proto.Bool(false), 59 OptInt32: proto.Int32(0), 60 OptInt64: proto.Int64(0), 61 OptUint32: proto.Uint32(0), 62 OptUint64: proto.Uint64(0), 63 OptSint32: proto.Int32(0), 64 OptSint64: proto.Int64(0), 65 OptFixed32: proto.Uint32(0), 66 OptFixed64: proto.Uint64(0), 67 OptSfixed32: proto.Int32(0), 68 OptSfixed64: proto.Int64(0), 69 OptFloat: proto.Float32(0), 70 OptDouble: proto.Float64(0), 71 OptBytes: []byte{}, 72 OptString: proto.String(""), 73 }, 74 want: `{ 75 "optBool": false, 76 "optInt32": 0, 77 "optInt64": "0", 78 "optUint32": 0, 79 "optUint64": "0", 80 "optSint32": 0, 81 "optSint64": "0", 82 "optFixed32": 0, 83 "optFixed64": "0", 84 "optSfixed32": 0, 85 "optSfixed64": "0", 86 "optFloat": 0, 87 "optDouble": 0, 88 "optBytes": "", 89 "optString": "" 90}`, 91 }, { 92 desc: "proto3 optional set to zero values", 93 input: &pb3.Proto3Optional{ 94 OptBool: proto.Bool(false), 95 OptInt32: proto.Int32(0), 96 OptInt64: proto.Int64(0), 97 OptUint32: proto.Uint32(0), 98 OptUint64: proto.Uint64(0), 99 OptFloat: proto.Float32(0), 100 OptDouble: proto.Float64(0), 101 OptString: proto.String(""), 102 OptBytes: []byte{}, 103 OptEnum: pb3.Enum_ZERO.Enum(), 104 OptMessage: &pb3.Nested{}, 105 }, 106 want: `{ 107 "optBool": false, 108 "optInt32": 0, 109 "optInt64": "0", 110 "optUint32": 0, 111 "optUint64": "0", 112 "optFloat": 0, 113 "optDouble": 0, 114 "optString": "", 115 "optBytes": "", 116 "optEnum": "ZERO", 117 "optMessage": {} 118}`, 119 }, { 120 desc: "proto2 optional scalars set to some values", 121 input: &pb2.Scalars{ 122 OptBool: proto.Bool(true), 123 OptInt32: proto.Int32(0xff), 124 OptInt64: proto.Int64(0xdeadbeef), 125 OptUint32: proto.Uint32(47), 126 OptUint64: proto.Uint64(0xdeadbeef), 127 OptSint32: proto.Int32(-1001), 128 OptSint64: proto.Int64(-0xffff), 129 OptFixed64: proto.Uint64(64), 130 OptSfixed32: proto.Int32(-32), 131 OptFloat: proto.Float32(1.02), 132 OptDouble: proto.Float64(1.234), 133 OptBytes: []byte("谷歌"), 134 OptString: proto.String("谷歌"), 135 }, 136 want: `{ 137 "optBool": true, 138 "optInt32": 255, 139 "optInt64": "3735928559", 140 "optUint32": 47, 141 "optUint64": "3735928559", 142 "optSint32": -1001, 143 "optSint64": "-65535", 144 "optFixed64": "64", 145 "optSfixed32": -32, 146 "optFloat": 1.02, 147 "optDouble": 1.234, 148 "optBytes": "6LC35q2M", 149 "optString": "谷歌" 150}`, 151 }, { 152 desc: "string", 153 input: &pb3.Scalars{ 154 SString: "谷歌", 155 }, 156 want: `{ 157 "sString": "谷歌" 158}`, 159 }, { 160 desc: "string with invalid UTF8", 161 input: &pb3.Scalars{ 162 SString: "abc\xff", 163 }, 164 wantErr: true, 165 }, { 166 desc: "float nan", 167 input: &pb3.Scalars{ 168 SFloat: float32(math.NaN()), 169 }, 170 want: `{ 171 "sFloat": "NaN" 172}`, 173 }, { 174 desc: "float positive infinity", 175 input: &pb3.Scalars{ 176 SFloat: float32(math.Inf(1)), 177 }, 178 want: `{ 179 "sFloat": "Infinity" 180}`, 181 }, { 182 desc: "float negative infinity", 183 input: &pb3.Scalars{ 184 SFloat: float32(math.Inf(-1)), 185 }, 186 want: `{ 187 "sFloat": "-Infinity" 188}`, 189 }, { 190 desc: "double nan", 191 input: &pb3.Scalars{ 192 SDouble: math.NaN(), 193 }, 194 want: `{ 195 "sDouble": "NaN" 196}`, 197 }, { 198 desc: "double positive infinity", 199 input: &pb3.Scalars{ 200 SDouble: math.Inf(1), 201 }, 202 want: `{ 203 "sDouble": "Infinity" 204}`, 205 }, { 206 desc: "double negative infinity", 207 input: &pb3.Scalars{ 208 SDouble: math.Inf(-1), 209 }, 210 want: `{ 211 "sDouble": "-Infinity" 212}`, 213 }, { 214 desc: "proto2 enum not set", 215 input: &pb2.Enums{}, 216 want: "{}", 217 }, { 218 desc: "proto2 enum set to zero value", 219 input: &pb2.Enums{ 220 OptEnum: pb2.Enum(0).Enum(), 221 OptNestedEnum: pb2.Enums_NestedEnum(0).Enum(), 222 }, 223 want: `{ 224 "optEnum": 0, 225 "optNestedEnum": 0 226}`, 227 }, { 228 desc: "proto2 enum", 229 input: &pb2.Enums{ 230 OptEnum: pb2.Enum_ONE.Enum(), 231 OptNestedEnum: pb2.Enums_UNO.Enum(), 232 }, 233 want: `{ 234 "optEnum": "ONE", 235 "optNestedEnum": "UNO" 236}`, 237 }, { 238 desc: "proto2 enum set to numeric values", 239 input: &pb2.Enums{ 240 OptEnum: pb2.Enum(2).Enum(), 241 OptNestedEnum: pb2.Enums_NestedEnum(2).Enum(), 242 }, 243 want: `{ 244 "optEnum": "TWO", 245 "optNestedEnum": "DOS" 246}`, 247 }, { 248 desc: "proto2 enum set to unnamed numeric values", 249 input: &pb2.Enums{ 250 OptEnum: pb2.Enum(101).Enum(), 251 OptNestedEnum: pb2.Enums_NestedEnum(-101).Enum(), 252 }, 253 want: `{ 254 "optEnum": 101, 255 "optNestedEnum": -101 256}`, 257 }, { 258 desc: "proto3 enum not set", 259 input: &pb3.Enums{}, 260 want: "{}", 261 }, { 262 desc: "proto3 enum set to zero value", 263 input: &pb3.Enums{ 264 SEnum: pb3.Enum_ZERO, 265 SNestedEnum: pb3.Enums_CERO, 266 }, 267 want: "{}", 268 }, { 269 desc: "proto3 enum", 270 input: &pb3.Enums{ 271 SEnum: pb3.Enum_ONE, 272 SNestedEnum: pb3.Enums_UNO, 273 }, 274 want: `{ 275 "sEnum": "ONE", 276 "sNestedEnum": "UNO" 277}`, 278 }, { 279 desc: "proto3 enum set to numeric values", 280 input: &pb3.Enums{ 281 SEnum: 2, 282 SNestedEnum: 2, 283 }, 284 want: `{ 285 "sEnum": "TWO", 286 "sNestedEnum": "DOS" 287}`, 288 }, { 289 desc: "proto3 enum set to unnamed numeric values", 290 input: &pb3.Enums{ 291 SEnum: -47, 292 SNestedEnum: 47, 293 }, 294 want: `{ 295 "sEnum": -47, 296 "sNestedEnum": 47 297}`, 298 }, { 299 desc: "proto2 nested message not set", 300 input: &pb2.Nests{}, 301 want: "{}", 302 }, { 303 desc: "proto2 nested message set to empty", 304 input: &pb2.Nests{ 305 OptNested: &pb2.Nested{}, 306 Optgroup: &pb2.Nests_OptGroup{}, 307 }, 308 want: `{ 309 "optNested": {}, 310 "optgroup": {} 311}`, 312 }, { 313 desc: "proto2 nested messages", 314 input: &pb2.Nests{ 315 OptNested: &pb2.Nested{ 316 OptString: proto.String("nested message"), 317 OptNested: &pb2.Nested{ 318 OptString: proto.String("another nested message"), 319 }, 320 }, 321 }, 322 want: `{ 323 "optNested": { 324 "optString": "nested message", 325 "optNested": { 326 "optString": "another nested message" 327 } 328 } 329}`, 330 }, { 331 desc: "proto2 groups", 332 input: &pb2.Nests{ 333 Optgroup: &pb2.Nests_OptGroup{ 334 OptString: proto.String("inside a group"), 335 OptNested: &pb2.Nested{ 336 OptString: proto.String("nested message inside a group"), 337 }, 338 Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{ 339 OptFixed32: proto.Uint32(47), 340 }, 341 }, 342 }, 343 want: `{ 344 "optgroup": { 345 "optString": "inside a group", 346 "optNested": { 347 "optString": "nested message inside a group" 348 }, 349 "optnestedgroup": { 350 "optFixed32": 47 351 } 352 } 353}`, 354 }, { 355 desc: "proto3 nested message not set", 356 input: &pb3.Nests{}, 357 want: "{}", 358 }, { 359 desc: "proto3 nested message set to empty", 360 input: &pb3.Nests{ 361 SNested: &pb3.Nested{}, 362 }, 363 want: `{ 364 "sNested": {} 365}`, 366 }, { 367 desc: "proto3 nested message", 368 input: &pb3.Nests{ 369 SNested: &pb3.Nested{ 370 SString: "nested message", 371 SNested: &pb3.Nested{ 372 SString: "another nested message", 373 }, 374 }, 375 }, 376 want: `{ 377 "sNested": { 378 "sString": "nested message", 379 "sNested": { 380 "sString": "another nested message" 381 } 382 } 383}`, 384 }, { 385 desc: "oneof not set", 386 input: &pb3.Oneofs{}, 387 want: "{}", 388 }, { 389 desc: "oneof set to empty string", 390 input: &pb3.Oneofs{ 391 Union: &pb3.Oneofs_OneofString{}, 392 }, 393 want: `{ 394 "oneofString": "" 395}`, 396 }, { 397 desc: "oneof set to string", 398 input: &pb3.Oneofs{ 399 Union: &pb3.Oneofs_OneofString{ 400 OneofString: "hello", 401 }, 402 }, 403 want: `{ 404 "oneofString": "hello" 405}`, 406 }, { 407 desc: "oneof set to enum", 408 input: &pb3.Oneofs{ 409 Union: &pb3.Oneofs_OneofEnum{ 410 OneofEnum: pb3.Enum_ZERO, 411 }, 412 }, 413 want: `{ 414 "oneofEnum": "ZERO" 415}`, 416 }, { 417 desc: "oneof set to empty message", 418 input: &pb3.Oneofs{ 419 Union: &pb3.Oneofs_OneofNested{ 420 OneofNested: &pb3.Nested{}, 421 }, 422 }, 423 want: `{ 424 "oneofNested": {} 425}`, 426 }, { 427 desc: "oneof set to message", 428 input: &pb3.Oneofs{ 429 Union: &pb3.Oneofs_OneofNested{ 430 OneofNested: &pb3.Nested{ 431 SString: "nested message", 432 }, 433 }, 434 }, 435 want: `{ 436 "oneofNested": { 437 "sString": "nested message" 438 } 439}`, 440 }, { 441 desc: "repeated fields not set", 442 input: &pb2.Repeats{}, 443 want: "{}", 444 }, { 445 desc: "repeated fields set to empty slices", 446 input: &pb2.Repeats{ 447 RptBool: []bool{}, 448 RptInt32: []int32{}, 449 RptInt64: []int64{}, 450 RptUint32: []uint32{}, 451 RptUint64: []uint64{}, 452 RptFloat: []float32{}, 453 RptDouble: []float64{}, 454 RptBytes: [][]byte{}, 455 }, 456 want: "{}", 457 }, { 458 desc: "repeated fields set to some values", 459 input: &pb2.Repeats{ 460 RptBool: []bool{true, false, true, true}, 461 RptInt32: []int32{1, 6, 0, 0}, 462 RptInt64: []int64{-64, 47}, 463 RptUint32: []uint32{0xff, 0xffff}, 464 RptUint64: []uint64{0xdeadbeef}, 465 RptFloat: []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034}, 466 RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308}, 467 RptString: []string{"hello", "世界"}, 468 RptBytes: [][]byte{ 469 []byte("hello"), 470 []byte("\xe4\xb8\x96\xe7\x95\x8c"), 471 }, 472 }, 473 want: `{ 474 "rptBool": [ 475 true, 476 false, 477 true, 478 true 479 ], 480 "rptInt32": [ 481 1, 482 6, 483 0, 484 0 485 ], 486 "rptInt64": [ 487 "-64", 488 "47" 489 ], 490 "rptUint32": [ 491 255, 492 65535 493 ], 494 "rptUint64": [ 495 "3735928559" 496 ], 497 "rptFloat": [ 498 "NaN", 499 "Infinity", 500 "-Infinity", 501 1.034 502 ], 503 "rptDouble": [ 504 "NaN", 505 "Infinity", 506 "-Infinity", 507 1.23e-308 508 ], 509 "rptString": [ 510 "hello", 511 "世界" 512 ], 513 "rptBytes": [ 514 "aGVsbG8=", 515 "5LiW55WM" 516 ] 517}`, 518 }, { 519 desc: "repeated enums", 520 input: &pb2.Enums{ 521 RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42}, 522 RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10}, 523 }, 524 want: `{ 525 "rptEnum": [ 526 "ONE", 527 "TWO", 528 "TEN", 529 42 530 ], 531 "rptNestedEnum": [ 532 "DOS", 533 47, 534 "DIEZ" 535 ] 536}`, 537 }, { 538 desc: "repeated messages set to empty", 539 input: &pb2.Nests{ 540 RptNested: []*pb2.Nested{}, 541 Rptgroup: []*pb2.Nests_RptGroup{}, 542 }, 543 want: "{}", 544 }, { 545 desc: "repeated messages", 546 input: &pb2.Nests{ 547 RptNested: []*pb2.Nested{ 548 { 549 OptString: proto.String("repeat nested one"), 550 }, 551 { 552 OptString: proto.String("repeat nested two"), 553 OptNested: &pb2.Nested{ 554 OptString: proto.String("inside repeat nested two"), 555 }, 556 }, 557 {}, 558 }, 559 }, 560 want: `{ 561 "rptNested": [ 562 { 563 "optString": "repeat nested one" 564 }, 565 { 566 "optString": "repeat nested two", 567 "optNested": { 568 "optString": "inside repeat nested two" 569 } 570 }, 571 {} 572 ] 573}`, 574 }, { 575 desc: "repeated messages contains nil value", 576 input: &pb2.Nests{ 577 RptNested: []*pb2.Nested{nil, {}}, 578 }, 579 want: `{ 580 "rptNested": [ 581 {}, 582 {} 583 ] 584}`, 585 }, { 586 desc: "repeated groups", 587 input: &pb2.Nests{ 588 Rptgroup: []*pb2.Nests_RptGroup{ 589 { 590 RptString: []string{"hello", "world"}, 591 }, 592 {}, 593 nil, 594 }, 595 }, 596 want: `{ 597 "rptgroup": [ 598 { 599 "rptString": [ 600 "hello", 601 "world" 602 ] 603 }, 604 {}, 605 {} 606 ] 607}`, 608 }, { 609 desc: "map fields not set", 610 input: &pb3.Maps{}, 611 want: "{}", 612 }, { 613 desc: "map fields set to empty", 614 input: &pb3.Maps{ 615 Int32ToStr: map[int32]string{}, 616 BoolToUint32: map[bool]uint32{}, 617 Uint64ToEnum: map[uint64]pb3.Enum{}, 618 StrToNested: map[string]*pb3.Nested{}, 619 StrToOneofs: map[string]*pb3.Oneofs{}, 620 }, 621 want: "{}", 622 }, { 623 desc: "map fields 1", 624 input: &pb3.Maps{ 625 BoolToUint32: map[bool]uint32{ 626 true: 42, 627 false: 101, 628 }, 629 }, 630 want: `{ 631 "boolToUint32": { 632 "false": 101, 633 "true": 42 634 } 635}`, 636 }, { 637 desc: "map fields 2", 638 input: &pb3.Maps{ 639 Int32ToStr: map[int32]string{ 640 -101: "-101", 641 0xff: "0xff", 642 0: "zero", 643 }, 644 }, 645 want: `{ 646 "int32ToStr": { 647 "-101": "-101", 648 "0": "zero", 649 "255": "0xff" 650 } 651}`, 652 }, { 653 desc: "map fields 3", 654 input: &pb3.Maps{ 655 Uint64ToEnum: map[uint64]pb3.Enum{ 656 1: pb3.Enum_ONE, 657 2: pb3.Enum_TWO, 658 10: pb3.Enum_TEN, 659 47: 47, 660 }, 661 }, 662 want: `{ 663 "uint64ToEnum": { 664 "1": "ONE", 665 "2": "TWO", 666 "10": "TEN", 667 "47": 47 668 } 669}`, 670 }, { 671 desc: "map fields 4", 672 input: &pb3.Maps{ 673 StrToNested: map[string]*pb3.Nested{ 674 "nested": &pb3.Nested{ 675 SString: "nested in a map", 676 }, 677 }, 678 }, 679 want: `{ 680 "strToNested": { 681 "nested": { 682 "sString": "nested in a map" 683 } 684 } 685}`, 686 }, { 687 desc: "map fields 5", 688 input: &pb3.Maps{ 689 StrToOneofs: map[string]*pb3.Oneofs{ 690 "string": &pb3.Oneofs{ 691 Union: &pb3.Oneofs_OneofString{ 692 OneofString: "hello", 693 }, 694 }, 695 "nested": &pb3.Oneofs{ 696 Union: &pb3.Oneofs_OneofNested{ 697 OneofNested: &pb3.Nested{ 698 SString: "nested oneof in map field value", 699 }, 700 }, 701 }, 702 }, 703 }, 704 want: `{ 705 "strToOneofs": { 706 "nested": { 707 "oneofNested": { 708 "sString": "nested oneof in map field value" 709 } 710 }, 711 "string": { 712 "oneofString": "hello" 713 } 714 } 715}`, 716 }, { 717 desc: "map field contains nil value", 718 input: &pb3.Maps{ 719 StrToNested: map[string]*pb3.Nested{ 720 "nil": nil, 721 }, 722 }, 723 want: `{ 724 "strToNested": { 725 "nil": {} 726 } 727}`, 728 }, { 729 desc: "required fields not set", 730 input: &pb2.Requireds{}, 731 want: `{}`, 732 wantErr: true, 733 }, { 734 desc: "required fields partially set", 735 input: &pb2.Requireds{ 736 ReqBool: proto.Bool(false), 737 ReqSfixed64: proto.Int64(0), 738 ReqDouble: proto.Float64(1.23), 739 ReqString: proto.String("hello"), 740 ReqEnum: pb2.Enum_ONE.Enum(), 741 }, 742 want: `{ 743 "reqBool": false, 744 "reqSfixed64": "0", 745 "reqDouble": 1.23, 746 "reqString": "hello", 747 "reqEnum": "ONE" 748}`, 749 wantErr: true, 750 }, { 751 desc: "required fields not set with AllowPartial", 752 mo: protojson.MarshalOptions{AllowPartial: true}, 753 input: &pb2.Requireds{ 754 ReqBool: proto.Bool(false), 755 ReqSfixed64: proto.Int64(0), 756 ReqDouble: proto.Float64(1.23), 757 ReqString: proto.String("hello"), 758 ReqEnum: pb2.Enum_ONE.Enum(), 759 }, 760 want: `{ 761 "reqBool": false, 762 "reqSfixed64": "0", 763 "reqDouble": 1.23, 764 "reqString": "hello", 765 "reqEnum": "ONE" 766}`, 767 }, { 768 desc: "required fields all set", 769 input: &pb2.Requireds{ 770 ReqBool: proto.Bool(false), 771 ReqSfixed64: proto.Int64(0), 772 ReqDouble: proto.Float64(1.23), 773 ReqString: proto.String("hello"), 774 ReqEnum: pb2.Enum_ONE.Enum(), 775 ReqNested: &pb2.Nested{}, 776 }, 777 want: `{ 778 "reqBool": false, 779 "reqSfixed64": "0", 780 "reqDouble": 1.23, 781 "reqString": "hello", 782 "reqEnum": "ONE", 783 "reqNested": {} 784}`, 785 }, { 786 desc: "indirect required field", 787 input: &pb2.IndirectRequired{ 788 OptNested: &pb2.NestedWithRequired{}, 789 }, 790 want: `{ 791 "optNested": {} 792}`, 793 wantErr: true, 794 }, { 795 desc: "indirect required field with AllowPartial", 796 mo: protojson.MarshalOptions{AllowPartial: true}, 797 input: &pb2.IndirectRequired{ 798 OptNested: &pb2.NestedWithRequired{}, 799 }, 800 want: `{ 801 "optNested": {} 802}`, 803 }, { 804 desc: "indirect required field in empty repeated", 805 input: &pb2.IndirectRequired{ 806 RptNested: []*pb2.NestedWithRequired{}, 807 }, 808 want: `{}`, 809 }, { 810 desc: "indirect required field in repeated", 811 input: &pb2.IndirectRequired{ 812 RptNested: []*pb2.NestedWithRequired{ 813 &pb2.NestedWithRequired{}, 814 }, 815 }, 816 want: `{ 817 "rptNested": [ 818 {} 819 ] 820}`, 821 wantErr: true, 822 }, { 823 desc: "indirect required field in repeated with AllowPartial", 824 mo: protojson.MarshalOptions{AllowPartial: true}, 825 input: &pb2.IndirectRequired{ 826 RptNested: []*pb2.NestedWithRequired{ 827 &pb2.NestedWithRequired{}, 828 }, 829 }, 830 want: `{ 831 "rptNested": [ 832 {} 833 ] 834}`, 835 }, { 836 desc: "indirect required field in empty map", 837 input: &pb2.IndirectRequired{ 838 StrToNested: map[string]*pb2.NestedWithRequired{}, 839 }, 840 want: "{}", 841 }, { 842 desc: "indirect required field in map", 843 input: &pb2.IndirectRequired{ 844 StrToNested: map[string]*pb2.NestedWithRequired{ 845 "fail": &pb2.NestedWithRequired{}, 846 }, 847 }, 848 want: `{ 849 "strToNested": { 850 "fail": {} 851 } 852}`, 853 wantErr: true, 854 }, { 855 desc: "indirect required field in map with AllowPartial", 856 mo: protojson.MarshalOptions{AllowPartial: true}, 857 input: &pb2.IndirectRequired{ 858 StrToNested: map[string]*pb2.NestedWithRequired{ 859 "fail": &pb2.NestedWithRequired{}, 860 }, 861 }, 862 want: `{ 863 "strToNested": { 864 "fail": {} 865 } 866}`, 867 }, { 868 desc: "indirect required field in oneof", 869 input: &pb2.IndirectRequired{ 870 Union: &pb2.IndirectRequired_OneofNested{ 871 OneofNested: &pb2.NestedWithRequired{}, 872 }, 873 }, 874 want: `{ 875 "oneofNested": {} 876}`, 877 wantErr: true, 878 }, { 879 desc: "indirect required field in oneof with AllowPartial", 880 mo: protojson.MarshalOptions{AllowPartial: true}, 881 input: &pb2.IndirectRequired{ 882 Union: &pb2.IndirectRequired_OneofNested{ 883 OneofNested: &pb2.NestedWithRequired{}, 884 }, 885 }, 886 want: `{ 887 "oneofNested": {} 888}`, 889 }, { 890 desc: "unknown fields are ignored", 891 input: func() proto.Message { 892 m := &pb2.Scalars{ 893 OptString: proto.String("no unknowns"), 894 } 895 m.ProtoReflect().SetUnknown(protopack.Message{ 896 protopack.Tag{101, protopack.BytesType}, protopack.String("hello world"), 897 }.Marshal()) 898 return m 899 }(), 900 want: `{ 901 "optString": "no unknowns" 902}`, 903 }, { 904 desc: "json_name", 905 input: &pb3.JSONNames{ 906 SString: "json_name", 907 }, 908 want: `{ 909 "foo_bar": "json_name" 910}`, 911 }, { 912 desc: "extensions of non-repeated fields", 913 input: func() proto.Message { 914 m := &pb2.Extensions{ 915 OptString: proto.String("non-extension field"), 916 OptBool: proto.Bool(true), 917 OptInt32: proto.Int32(42), 918 } 919 proto.SetExtension(m, pb2.E_OptExtBool, true) 920 proto.SetExtension(m, pb2.E_OptExtString, "extension field") 921 proto.SetExtension(m, pb2.E_OptExtEnum, pb2.Enum_TEN) 922 proto.SetExtension(m, pb2.E_OptExtNested, &pb2.Nested{ 923 OptString: proto.String("nested in an extension"), 924 OptNested: &pb2.Nested{ 925 OptString: proto.String("another nested in an extension"), 926 }, 927 }) 928 return m 929 }(), 930 want: `{ 931 "optString": "non-extension field", 932 "optBool": true, 933 "optInt32": 42, 934 "[pb2.opt_ext_bool]": true, 935 "[pb2.opt_ext_enum]": "TEN", 936 "[pb2.opt_ext_nested]": { 937 "optString": "nested in an extension", 938 "optNested": { 939 "optString": "another nested in an extension" 940 } 941 }, 942 "[pb2.opt_ext_string]": "extension field" 943}`, 944 }, { 945 desc: "extensions of repeated fields", 946 input: func() proto.Message { 947 m := &pb2.Extensions{} 948 proto.SetExtension(m, pb2.E_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE}) 949 proto.SetExtension(m, pb2.E_RptExtFixed32, []uint32{42, 47}) 950 proto.SetExtension(m, pb2.E_RptExtNested, []*pb2.Nested{ 951 &pb2.Nested{OptString: proto.String("one")}, 952 &pb2.Nested{OptString: proto.String("two")}, 953 &pb2.Nested{OptString: proto.String("three")}, 954 }) 955 return m 956 }(), 957 want: `{ 958 "[pb2.rpt_ext_enum]": [ 959 "TEN", 960 101, 961 "ONE" 962 ], 963 "[pb2.rpt_ext_fixed32]": [ 964 42, 965 47 966 ], 967 "[pb2.rpt_ext_nested]": [ 968 { 969 "optString": "one" 970 }, 971 { 972 "optString": "two" 973 }, 974 { 975 "optString": "three" 976 } 977 ] 978}`, 979 }, { 980 desc: "extensions of non-repeated fields in another message", 981 input: func() proto.Message { 982 m := &pb2.Extensions{} 983 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtBool, true) 984 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtString, "extension field") 985 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtEnum, pb2.Enum_TEN) 986 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtNested, &pb2.Nested{ 987 OptString: proto.String("nested in an extension"), 988 OptNested: &pb2.Nested{ 989 OptString: proto.String("another nested in an extension"), 990 }, 991 }) 992 return m 993 }(), 994 want: `{ 995 "[pb2.ExtensionsContainer.opt_ext_bool]": true, 996 "[pb2.ExtensionsContainer.opt_ext_enum]": "TEN", 997 "[pb2.ExtensionsContainer.opt_ext_nested]": { 998 "optString": "nested in an extension", 999 "optNested": { 1000 "optString": "another nested in an extension" 1001 } 1002 }, 1003 "[pb2.ExtensionsContainer.opt_ext_string]": "extension field" 1004}`, 1005 }, { 1006 desc: "extensions of repeated fields in another message", 1007 input: func() proto.Message { 1008 m := &pb2.Extensions{ 1009 OptString: proto.String("non-extension field"), 1010 OptBool: proto.Bool(true), 1011 OptInt32: proto.Int32(42), 1012 } 1013 proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE}) 1014 proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtString, []string{"hello", "world"}) 1015 proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtNested, []*pb2.Nested{ 1016 &pb2.Nested{OptString: proto.String("one")}, 1017 &pb2.Nested{OptString: proto.String("two")}, 1018 &pb2.Nested{OptString: proto.String("three")}, 1019 }) 1020 return m 1021 }(), 1022 want: `{ 1023 "optString": "non-extension field", 1024 "optBool": true, 1025 "optInt32": 42, 1026 "[pb2.ExtensionsContainer.rpt_ext_enum]": [ 1027 "TEN", 1028 101, 1029 "ONE" 1030 ], 1031 "[pb2.ExtensionsContainer.rpt_ext_nested]": [ 1032 { 1033 "optString": "one" 1034 }, 1035 { 1036 "optString": "two" 1037 }, 1038 { 1039 "optString": "three" 1040 } 1041 ], 1042 "[pb2.ExtensionsContainer.rpt_ext_string]": [ 1043 "hello", 1044 "world" 1045 ] 1046}`, 1047 }, { 1048 desc: "MessageSet", 1049 input: func() proto.Message { 1050 m := &pb2.MessageSet{} 1051 proto.SetExtension(m, pb2.E_MessageSetExtension_MessageSetExtension, &pb2.MessageSetExtension{ 1052 OptString: proto.String("a messageset extension"), 1053 }) 1054 proto.SetExtension(m, pb2.E_MessageSetExtension_NotMessageSetExtension, &pb2.MessageSetExtension{ 1055 OptString: proto.String("not a messageset extension"), 1056 }) 1057 proto.SetExtension(m, pb2.E_MessageSetExtension_ExtNested, &pb2.Nested{ 1058 OptString: proto.String("just a regular extension"), 1059 }) 1060 return m 1061 }(), 1062 want: `{ 1063 "[pb2.MessageSetExtension.ext_nested]": { 1064 "optString": "just a regular extension" 1065 }, 1066 "[pb2.MessageSetExtension]": { 1067 "optString": "a messageset extension" 1068 }, 1069 "[pb2.MessageSetExtension.not_message_set_extension]": { 1070 "optString": "not a messageset extension" 1071 } 1072}`, 1073 skip: !flags.ProtoLegacy, 1074 }, { 1075 desc: "not real MessageSet 1", 1076 input: func() proto.Message { 1077 m := &pb2.FakeMessageSet{} 1078 proto.SetExtension(m, pb2.E_FakeMessageSetExtension_MessageSetExtension, &pb2.FakeMessageSetExtension{ 1079 OptString: proto.String("not a messageset extension"), 1080 }) 1081 return m 1082 }(), 1083 want: `{ 1084 "[pb2.FakeMessageSetExtension.message_set_extension]": { 1085 "optString": "not a messageset extension" 1086 } 1087}`, 1088 skip: !flags.ProtoLegacy, 1089 }, { 1090 desc: "not real MessageSet 2", 1091 input: func() proto.Message { 1092 m := &pb2.MessageSet{} 1093 proto.SetExtension(m, pb2.E_MessageSetExtension, &pb2.FakeMessageSetExtension{ 1094 OptString: proto.String("another not a messageset extension"), 1095 }) 1096 return m 1097 }(), 1098 want: `{ 1099 "[pb2.message_set_extension]": { 1100 "optString": "another not a messageset extension" 1101 } 1102}`, 1103 skip: !flags.ProtoLegacy, 1104 }, { 1105 desc: "BoolValue empty", 1106 input: &wrapperspb.BoolValue{}, 1107 want: `false`, 1108 }, { 1109 desc: "BoolValue", 1110 input: &wrapperspb.BoolValue{Value: true}, 1111 want: `true`, 1112 }, { 1113 desc: "Int32Value empty", 1114 input: &wrapperspb.Int32Value{}, 1115 want: `0`, 1116 }, { 1117 desc: "Int32Value", 1118 input: &wrapperspb.Int32Value{Value: 42}, 1119 want: `42`, 1120 }, { 1121 desc: "Int64Value", 1122 input: &wrapperspb.Int64Value{Value: 42}, 1123 want: `"42"`, 1124 }, { 1125 desc: "UInt32Value", 1126 input: &wrapperspb.UInt32Value{Value: 42}, 1127 want: `42`, 1128 }, { 1129 desc: "UInt64Value", 1130 input: &wrapperspb.UInt64Value{Value: 42}, 1131 want: `"42"`, 1132 }, { 1133 desc: "FloatValue", 1134 input: &wrapperspb.FloatValue{Value: 1.02}, 1135 want: `1.02`, 1136 }, { 1137 desc: "FloatValue Infinity", 1138 input: &wrapperspb.FloatValue{Value: float32(math.Inf(-1))}, 1139 want: `"-Infinity"`, 1140 }, { 1141 desc: "DoubleValue", 1142 input: &wrapperspb.DoubleValue{Value: 1.02}, 1143 want: `1.02`, 1144 }, { 1145 desc: "DoubleValue NaN", 1146 input: &wrapperspb.DoubleValue{Value: math.NaN()}, 1147 want: `"NaN"`, 1148 }, { 1149 desc: "StringValue empty", 1150 input: &wrapperspb.StringValue{}, 1151 want: `""`, 1152 }, { 1153 desc: "StringValue", 1154 input: &wrapperspb.StringValue{Value: "谷歌"}, 1155 want: `"谷歌"`, 1156 }, { 1157 desc: "StringValue with invalid UTF8 error", 1158 input: &wrapperspb.StringValue{Value: "abc\xff"}, 1159 wantErr: true, 1160 }, { 1161 desc: "StringValue field with invalid UTF8 error", 1162 input: &pb2.KnownTypes{ 1163 OptString: &wrapperspb.StringValue{Value: "abc\xff"}, 1164 }, 1165 wantErr: true, 1166 }, { 1167 desc: "BytesValue", 1168 input: &wrapperspb.BytesValue{Value: []byte("hello")}, 1169 want: `"aGVsbG8="`, 1170 }, { 1171 desc: "Empty", 1172 input: &emptypb.Empty{}, 1173 want: `{}`, 1174 }, { 1175 desc: "NullValue field", 1176 input: &pb2.KnownTypes{OptNull: new(structpb.NullValue)}, 1177 want: `{ 1178 "optNull": null 1179}`, 1180 }, { 1181 desc: "Value empty", 1182 input: &structpb.Value{}, 1183 wantErr: true, 1184 }, { 1185 desc: "Value empty field", 1186 input: &pb2.KnownTypes{ 1187 OptValue: &structpb.Value{}, 1188 }, 1189 wantErr: true, 1190 }, { 1191 desc: "Value contains NullValue", 1192 input: &structpb.Value{Kind: &structpb.Value_NullValue{}}, 1193 want: `null`, 1194 }, { 1195 desc: "Value contains BoolValue", 1196 input: &structpb.Value{Kind: &structpb.Value_BoolValue{}}, 1197 want: `false`, 1198 }, { 1199 desc: "Value contains NumberValue", 1200 input: &structpb.Value{Kind: &structpb.Value_NumberValue{1.02}}, 1201 want: `1.02`, 1202 }, { 1203 desc: "Value contains StringValue", 1204 input: &structpb.Value{Kind: &structpb.Value_StringValue{"hello"}}, 1205 want: `"hello"`, 1206 }, { 1207 desc: "Value contains StringValue with invalid UTF8", 1208 input: &structpb.Value{Kind: &structpb.Value_StringValue{"\xff"}}, 1209 wantErr: true, 1210 }, { 1211 desc: "Value contains Struct", 1212 input: &structpb.Value{ 1213 Kind: &structpb.Value_StructValue{ 1214 &structpb.Struct{ 1215 Fields: map[string]*structpb.Value{ 1216 "null": {Kind: &structpb.Value_NullValue{}}, 1217 "number": {Kind: &structpb.Value_NumberValue{}}, 1218 "string": {Kind: &structpb.Value_StringValue{}}, 1219 "struct": {Kind: &structpb.Value_StructValue{}}, 1220 "list": {Kind: &structpb.Value_ListValue{}}, 1221 "bool": {Kind: &structpb.Value_BoolValue{}}, 1222 }, 1223 }, 1224 }, 1225 }, 1226 want: `{ 1227 "bool": false, 1228 "list": [], 1229 "null": null, 1230 "number": 0, 1231 "string": "", 1232 "struct": {} 1233}`, 1234 }, { 1235 desc: "Value contains ListValue", 1236 input: &structpb.Value{ 1237 Kind: &structpb.Value_ListValue{ 1238 &structpb.ListValue{ 1239 Values: []*structpb.Value{ 1240 {Kind: &structpb.Value_BoolValue{}}, 1241 {Kind: &structpb.Value_NullValue{}}, 1242 {Kind: &structpb.Value_NumberValue{}}, 1243 {Kind: &structpb.Value_StringValue{}}, 1244 {Kind: &structpb.Value_StructValue{}}, 1245 {Kind: &structpb.Value_ListValue{}}, 1246 }, 1247 }, 1248 }, 1249 }, 1250 want: `[ 1251 false, 1252 null, 1253 0, 1254 "", 1255 {}, 1256 [] 1257]`, 1258 }, { 1259 desc: "Value with NaN", 1260 input: structpb.NewNumberValue(math.NaN()), 1261 wantErr: true, 1262 }, { 1263 desc: "Value with -Inf", 1264 input: structpb.NewNumberValue(math.Inf(-1)), 1265 wantErr: true, 1266 }, { 1267 desc: "Value with +Inf", 1268 input: structpb.NewNumberValue(math.Inf(+1)), 1269 wantErr: true, 1270 }, { 1271 desc: "Struct with nil map", 1272 input: &structpb.Struct{}, 1273 want: `{}`, 1274 }, { 1275 desc: "Struct with empty map", 1276 input: &structpb.Struct{ 1277 Fields: map[string]*structpb.Value{}, 1278 }, 1279 want: `{}`, 1280 }, { 1281 desc: "Struct", 1282 input: &structpb.Struct{ 1283 Fields: map[string]*structpb.Value{ 1284 "bool": {Kind: &structpb.Value_BoolValue{true}}, 1285 "null": {Kind: &structpb.Value_NullValue{}}, 1286 "number": {Kind: &structpb.Value_NumberValue{3.1415}}, 1287 "string": {Kind: &structpb.Value_StringValue{"hello"}}, 1288 "struct": { 1289 Kind: &structpb.Value_StructValue{ 1290 &structpb.Struct{ 1291 Fields: map[string]*structpb.Value{ 1292 "string": {Kind: &structpb.Value_StringValue{"world"}}, 1293 }, 1294 }, 1295 }, 1296 }, 1297 "list": { 1298 Kind: &structpb.Value_ListValue{ 1299 &structpb.ListValue{ 1300 Values: []*structpb.Value{ 1301 {Kind: &structpb.Value_BoolValue{}}, 1302 {Kind: &structpb.Value_NullValue{}}, 1303 {Kind: &structpb.Value_NumberValue{}}, 1304 }, 1305 }, 1306 }, 1307 }, 1308 }, 1309 }, 1310 want: `{ 1311 "bool": true, 1312 "list": [ 1313 false, 1314 null, 1315 0 1316 ], 1317 "null": null, 1318 "number": 3.1415, 1319 "string": "hello", 1320 "struct": { 1321 "string": "world" 1322 } 1323}`, 1324 }, { 1325 desc: "Struct message with invalid UTF8 string", 1326 input: &structpb.Struct{ 1327 Fields: map[string]*structpb.Value{ 1328 "string": {Kind: &structpb.Value_StringValue{"\xff"}}, 1329 }, 1330 }, 1331 wantErr: true, 1332 }, { 1333 desc: "ListValue with nil values", 1334 input: &structpb.ListValue{}, 1335 want: `[]`, 1336 }, { 1337 desc: "ListValue with empty values", 1338 input: &structpb.ListValue{ 1339 Values: []*structpb.Value{}, 1340 }, 1341 want: `[]`, 1342 }, { 1343 desc: "ListValue", 1344 input: &structpb.ListValue{ 1345 Values: []*structpb.Value{ 1346 {Kind: &structpb.Value_BoolValue{true}}, 1347 {Kind: &structpb.Value_NullValue{}}, 1348 {Kind: &structpb.Value_NumberValue{3.1415}}, 1349 {Kind: &structpb.Value_StringValue{"hello"}}, 1350 { 1351 Kind: &structpb.Value_ListValue{ 1352 &structpb.ListValue{ 1353 Values: []*structpb.Value{ 1354 {Kind: &structpb.Value_BoolValue{}}, 1355 {Kind: &structpb.Value_NullValue{}}, 1356 {Kind: &structpb.Value_NumberValue{}}, 1357 }, 1358 }, 1359 }, 1360 }, 1361 { 1362 Kind: &structpb.Value_StructValue{ 1363 &structpb.Struct{ 1364 Fields: map[string]*structpb.Value{ 1365 "string": {Kind: &structpb.Value_StringValue{"world"}}, 1366 }, 1367 }, 1368 }, 1369 }, 1370 }, 1371 }, 1372 want: `[ 1373 true, 1374 null, 1375 3.1415, 1376 "hello", 1377 [ 1378 false, 1379 null, 1380 0 1381 ], 1382 { 1383 "string": "world" 1384 } 1385]`, 1386 }, { 1387 desc: "ListValue with invalid UTF8 string", 1388 input: &structpb.ListValue{ 1389 Values: []*structpb.Value{ 1390 {Kind: &structpb.Value_StringValue{"\xff"}}, 1391 }, 1392 }, 1393 wantErr: true, 1394 }, { 1395 desc: "Duration empty", 1396 input: &durationpb.Duration{}, 1397 want: `"0s"`, 1398 }, { 1399 desc: "Duration with secs", 1400 input: &durationpb.Duration{Seconds: 3}, 1401 want: `"3s"`, 1402 }, { 1403 desc: "Duration with -secs", 1404 input: &durationpb.Duration{Seconds: -3}, 1405 want: `"-3s"`, 1406 }, { 1407 desc: "Duration with nanos", 1408 input: &durationpb.Duration{Nanos: 1e6}, 1409 want: `"0.001s"`, 1410 }, { 1411 desc: "Duration with -nanos", 1412 input: &durationpb.Duration{Nanos: -1e6}, 1413 want: `"-0.001s"`, 1414 }, { 1415 desc: "Duration with large secs", 1416 input: &durationpb.Duration{Seconds: 1e10, Nanos: 1}, 1417 want: `"10000000000.000000001s"`, 1418 }, { 1419 desc: "Duration with 6-digit nanos", 1420 input: &durationpb.Duration{Nanos: 1e4}, 1421 want: `"0.000010s"`, 1422 }, { 1423 desc: "Duration with 3-digit nanos", 1424 input: &durationpb.Duration{Nanos: 1e6}, 1425 want: `"0.001s"`, 1426 }, { 1427 desc: "Duration with -secs -nanos", 1428 input: &durationpb.Duration{Seconds: -123, Nanos: -450}, 1429 want: `"-123.000000450s"`, 1430 }, { 1431 desc: "Duration max value", 1432 input: &durationpb.Duration{Seconds: 315576000000, Nanos: 999999999}, 1433 want: `"315576000000.999999999s"`, 1434 }, { 1435 desc: "Duration min value", 1436 input: &durationpb.Duration{Seconds: -315576000000, Nanos: -999999999}, 1437 want: `"-315576000000.999999999s"`, 1438 }, { 1439 desc: "Duration with +secs -nanos", 1440 input: &durationpb.Duration{Seconds: 1, Nanos: -1}, 1441 wantErr: true, 1442 }, { 1443 desc: "Duration with -secs +nanos", 1444 input: &durationpb.Duration{Seconds: -1, Nanos: 1}, 1445 wantErr: true, 1446 }, { 1447 desc: "Duration with +secs out of range", 1448 input: &durationpb.Duration{Seconds: 315576000001}, 1449 wantErr: true, 1450 }, { 1451 desc: "Duration with -secs out of range", 1452 input: &durationpb.Duration{Seconds: -315576000001}, 1453 wantErr: true, 1454 }, { 1455 desc: "Duration with +nanos out of range", 1456 input: &durationpb.Duration{Seconds: 0, Nanos: 1e9}, 1457 wantErr: true, 1458 }, { 1459 desc: "Duration with -nanos out of range", 1460 input: &durationpb.Duration{Seconds: 0, Nanos: -1e9}, 1461 wantErr: true, 1462 }, { 1463 desc: "Timestamp zero", 1464 input: ×tamppb.Timestamp{}, 1465 want: `"1970-01-01T00:00:00Z"`, 1466 }, { 1467 desc: "Timestamp", 1468 input: ×tamppb.Timestamp{Seconds: 1553036601}, 1469 want: `"2019-03-19T23:03:21Z"`, 1470 }, { 1471 desc: "Timestamp with nanos", 1472 input: ×tamppb.Timestamp{Seconds: 1553036601, Nanos: 1}, 1473 want: `"2019-03-19T23:03:21.000000001Z"`, 1474 }, { 1475 desc: "Timestamp with 6-digit nanos", 1476 input: ×tamppb.Timestamp{Nanos: 1e3}, 1477 want: `"1970-01-01T00:00:00.000001Z"`, 1478 }, { 1479 desc: "Timestamp with 3-digit nanos", 1480 input: ×tamppb.Timestamp{Nanos: 1e7}, 1481 want: `"1970-01-01T00:00:00.010Z"`, 1482 }, { 1483 desc: "Timestamp max value", 1484 input: ×tamppb.Timestamp{Seconds: 253402300799, Nanos: 999999999}, 1485 want: `"9999-12-31T23:59:59.999999999Z"`, 1486 }, { 1487 desc: "Timestamp min value", 1488 input: ×tamppb.Timestamp{Seconds: -62135596800}, 1489 want: `"0001-01-01T00:00:00Z"`, 1490 }, { 1491 desc: "Timestamp with +secs out of range", 1492 input: ×tamppb.Timestamp{Seconds: 253402300800}, 1493 wantErr: true, 1494 }, { 1495 desc: "Timestamp with -secs out of range", 1496 input: ×tamppb.Timestamp{Seconds: -62135596801}, 1497 wantErr: true, 1498 }, { 1499 desc: "Timestamp with -nanos", 1500 input: ×tamppb.Timestamp{Nanos: -1}, 1501 wantErr: true, 1502 }, { 1503 desc: "Timestamp with +nanos out of range", 1504 input: ×tamppb.Timestamp{Nanos: 1e9}, 1505 wantErr: true, 1506 }, { 1507 desc: "FieldMask empty", 1508 input: &fieldmaskpb.FieldMask{}, 1509 want: `""`, 1510 }, { 1511 desc: "FieldMask", 1512 input: &fieldmaskpb.FieldMask{ 1513 Paths: []string{ 1514 "foo", 1515 "foo_bar", 1516 "foo.bar_qux", 1517 "_foo", 1518 }, 1519 }, 1520 want: `"foo,fooBar,foo.barQux,Foo"`, 1521 }, { 1522 desc: "FieldMask empty string path", 1523 input: &fieldmaskpb.FieldMask{ 1524 Paths: []string{""}, 1525 }, 1526 wantErr: true, 1527 }, { 1528 desc: "FieldMask path contains spaces only", 1529 input: &fieldmaskpb.FieldMask{ 1530 Paths: []string{" "}, 1531 }, 1532 wantErr: true, 1533 }, { 1534 desc: "FieldMask irreversible error 1", 1535 input: &fieldmaskpb.FieldMask{ 1536 Paths: []string{"foo_"}, 1537 }, 1538 wantErr: true, 1539 }, { 1540 desc: "FieldMask irreversible error 2", 1541 input: &fieldmaskpb.FieldMask{ 1542 Paths: []string{"foo__bar"}, 1543 }, 1544 wantErr: true, 1545 }, { 1546 desc: "FieldMask invalid char", 1547 input: &fieldmaskpb.FieldMask{ 1548 Paths: []string{"foo@bar"}, 1549 }, 1550 wantErr: true, 1551 }, { 1552 desc: "Any empty", 1553 input: &anypb.Any{}, 1554 want: `{}`, 1555 }, { 1556 desc: "Any with non-custom message", 1557 input: func() proto.Message { 1558 m := &pb2.Nested{ 1559 OptString: proto.String("embedded inside Any"), 1560 OptNested: &pb2.Nested{ 1561 OptString: proto.String("inception"), 1562 }, 1563 } 1564 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1565 if err != nil { 1566 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1567 } 1568 return &anypb.Any{ 1569 TypeUrl: "foo/pb2.Nested", 1570 Value: b, 1571 } 1572 }(), 1573 want: `{ 1574 "@type": "foo/pb2.Nested", 1575 "optString": "embedded inside Any", 1576 "optNested": { 1577 "optString": "inception" 1578 } 1579}`, 1580 }, { 1581 desc: "Any with empty embedded message", 1582 input: &anypb.Any{TypeUrl: "foo/pb2.Nested"}, 1583 want: `{ 1584 "@type": "foo/pb2.Nested" 1585}`, 1586 }, { 1587 desc: "Any without registered type", 1588 mo: protojson.MarshalOptions{Resolver: new(protoregistry.Types)}, 1589 input: &anypb.Any{TypeUrl: "foo/pb2.Nested"}, 1590 wantErr: true, 1591 }, { 1592 desc: "Any with missing required", 1593 input: func() proto.Message { 1594 m := &pb2.PartialRequired{ 1595 OptString: proto.String("embedded inside Any"), 1596 } 1597 b, err := proto.MarshalOptions{ 1598 AllowPartial: true, 1599 Deterministic: true, 1600 }.Marshal(m) 1601 if err != nil { 1602 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1603 } 1604 return &anypb.Any{ 1605 TypeUrl: string(m.ProtoReflect().Descriptor().FullName()), 1606 Value: b, 1607 } 1608 }(), 1609 want: `{ 1610 "@type": "pb2.PartialRequired", 1611 "optString": "embedded inside Any" 1612}`, 1613 }, { 1614 desc: "Any with partial required and AllowPartial", 1615 mo: protojson.MarshalOptions{ 1616 AllowPartial: true, 1617 }, 1618 input: func() proto.Message { 1619 m := &pb2.PartialRequired{ 1620 OptString: proto.String("embedded inside Any"), 1621 } 1622 b, err := proto.MarshalOptions{ 1623 AllowPartial: true, 1624 Deterministic: true, 1625 }.Marshal(m) 1626 if err != nil { 1627 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1628 } 1629 return &anypb.Any{ 1630 TypeUrl: string(m.ProtoReflect().Descriptor().FullName()), 1631 Value: b, 1632 } 1633 }(), 1634 want: `{ 1635 "@type": "pb2.PartialRequired", 1636 "optString": "embedded inside Any" 1637}`, 1638 }, { 1639 desc: "Any with EmitUnpopulated", 1640 mo: protojson.MarshalOptions{ 1641 EmitUnpopulated: true, 1642 }, 1643 input: func() proto.Message { 1644 return &anypb.Any{ 1645 TypeUrl: string(new(pb3.Scalars).ProtoReflect().Descriptor().FullName()), 1646 } 1647 }(), 1648 want: `{ 1649 "@type": "pb3.Scalars", 1650 "sBool": false, 1651 "sInt32": 0, 1652 "sInt64": "0", 1653 "sUint32": 0, 1654 "sUint64": "0", 1655 "sSint32": 0, 1656 "sSint64": "0", 1657 "sFixed32": 0, 1658 "sFixed64": "0", 1659 "sSfixed32": 0, 1660 "sSfixed64": "0", 1661 "sFloat": 0, 1662 "sDouble": 0, 1663 "sBytes": "", 1664 "sString": "" 1665}`, 1666 }, { 1667 desc: "Any with invalid UTF8", 1668 input: func() proto.Message { 1669 m := &pb2.Nested{ 1670 OptString: proto.String("abc\xff"), 1671 } 1672 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1673 if err != nil { 1674 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1675 } 1676 return &anypb.Any{ 1677 TypeUrl: "foo/pb2.Nested", 1678 Value: b, 1679 } 1680 }(), 1681 wantErr: true, 1682 }, { 1683 desc: "Any with invalid value", 1684 input: &anypb.Any{ 1685 TypeUrl: "foo/pb2.Nested", 1686 Value: []byte("\x80"), 1687 }, 1688 wantErr: true, 1689 }, { 1690 desc: "Any with BoolValue", 1691 input: func() proto.Message { 1692 m := &wrapperspb.BoolValue{Value: true} 1693 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1694 if err != nil { 1695 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1696 } 1697 return &anypb.Any{ 1698 TypeUrl: "type.googleapis.com/google.protobuf.BoolValue", 1699 Value: b, 1700 } 1701 }(), 1702 want: `{ 1703 "@type": "type.googleapis.com/google.protobuf.BoolValue", 1704 "value": true 1705}`, 1706 }, { 1707 desc: "Any with Empty", 1708 input: func() proto.Message { 1709 m := &emptypb.Empty{} 1710 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1711 if err != nil { 1712 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1713 } 1714 return &anypb.Any{ 1715 TypeUrl: "type.googleapis.com/google.protobuf.Empty", 1716 Value: b, 1717 } 1718 }(), 1719 want: `{ 1720 "@type": "type.googleapis.com/google.protobuf.Empty", 1721 "value": {} 1722}`, 1723 }, { 1724 desc: "Any with StringValue containing invalid UTF8", 1725 input: func() proto.Message { 1726 m := &wrapperspb.StringValue{Value: "abcd"} 1727 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1728 if err != nil { 1729 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1730 } 1731 return &anypb.Any{ 1732 TypeUrl: "google.protobuf.StringValue", 1733 Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1), 1734 } 1735 }(), 1736 wantErr: true, 1737 }, { 1738 desc: "Any with Int64Value", 1739 input: func() proto.Message { 1740 m := &wrapperspb.Int64Value{Value: 42} 1741 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1742 if err != nil { 1743 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1744 } 1745 return &anypb.Any{ 1746 TypeUrl: "google.protobuf.Int64Value", 1747 Value: b, 1748 } 1749 }(), 1750 want: `{ 1751 "@type": "google.protobuf.Int64Value", 1752 "value": "42" 1753}`, 1754 }, { 1755 desc: "Any with Duration", 1756 input: func() proto.Message { 1757 m := &durationpb.Duration{} 1758 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1759 if err != nil { 1760 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1761 } 1762 return &anypb.Any{ 1763 TypeUrl: "type.googleapis.com/google.protobuf.Duration", 1764 Value: b, 1765 } 1766 }(), 1767 want: `{ 1768 "@type": "type.googleapis.com/google.protobuf.Duration", 1769 "value": "0s" 1770}`, 1771 }, { 1772 desc: "Any with empty Value", 1773 input: func() proto.Message { 1774 m := &structpb.Value{} 1775 b, err := proto.Marshal(m) 1776 if err != nil { 1777 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1778 } 1779 return &anypb.Any{ 1780 TypeUrl: "type.googleapis.com/google.protobuf.Value", 1781 Value: b, 1782 } 1783 }(), 1784 wantErr: true, 1785 }, { 1786 desc: "Any with Value of StringValue", 1787 input: func() proto.Message { 1788 m := &structpb.Value{Kind: &structpb.Value_StringValue{"abcd"}} 1789 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1790 if err != nil { 1791 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1792 } 1793 return &anypb.Any{ 1794 TypeUrl: "type.googleapis.com/google.protobuf.Value", 1795 Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1), 1796 } 1797 }(), 1798 wantErr: true, 1799 }, { 1800 desc: "Any with Value of NullValue", 1801 input: func() proto.Message { 1802 m := &structpb.Value{Kind: &structpb.Value_NullValue{}} 1803 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1804 if err != nil { 1805 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1806 } 1807 return &anypb.Any{ 1808 TypeUrl: "type.googleapis.com/google.protobuf.Value", 1809 Value: b, 1810 } 1811 }(), 1812 want: `{ 1813 "@type": "type.googleapis.com/google.protobuf.Value", 1814 "value": null 1815}`, 1816 }, { 1817 desc: "Any with Struct", 1818 input: func() proto.Message { 1819 m := &structpb.Struct{ 1820 Fields: map[string]*structpb.Value{ 1821 "bool": {Kind: &structpb.Value_BoolValue{true}}, 1822 "null": {Kind: &structpb.Value_NullValue{}}, 1823 "string": {Kind: &structpb.Value_StringValue{"hello"}}, 1824 "struct": { 1825 Kind: &structpb.Value_StructValue{ 1826 &structpb.Struct{ 1827 Fields: map[string]*structpb.Value{ 1828 "string": {Kind: &structpb.Value_StringValue{"world"}}, 1829 }, 1830 }, 1831 }, 1832 }, 1833 }, 1834 } 1835 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1836 if err != nil { 1837 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1838 } 1839 return &anypb.Any{ 1840 TypeUrl: "google.protobuf.Struct", 1841 Value: b, 1842 } 1843 }(), 1844 want: `{ 1845 "@type": "google.protobuf.Struct", 1846 "value": { 1847 "bool": true, 1848 "null": null, 1849 "string": "hello", 1850 "struct": { 1851 "string": "world" 1852 } 1853 } 1854}`, 1855 }, { 1856 desc: "Any with missing type_url", 1857 input: func() proto.Message { 1858 m := &wrapperspb.BoolValue{Value: true} 1859 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1860 if err != nil { 1861 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1862 } 1863 return &anypb.Any{ 1864 Value: b, 1865 } 1866 }(), 1867 wantErr: true, 1868 }, { 1869 desc: "well known types as field values", 1870 input: &pb2.KnownTypes{ 1871 OptBool: &wrapperspb.BoolValue{Value: false}, 1872 OptInt32: &wrapperspb.Int32Value{Value: 42}, 1873 OptInt64: &wrapperspb.Int64Value{Value: 42}, 1874 OptUint32: &wrapperspb.UInt32Value{Value: 42}, 1875 OptUint64: &wrapperspb.UInt64Value{Value: 42}, 1876 OptFloat: &wrapperspb.FloatValue{Value: 1.23}, 1877 OptDouble: &wrapperspb.DoubleValue{Value: 3.1415}, 1878 OptString: &wrapperspb.StringValue{Value: "hello"}, 1879 OptBytes: &wrapperspb.BytesValue{Value: []byte("hello")}, 1880 OptDuration: &durationpb.Duration{Seconds: 123}, 1881 OptTimestamp: ×tamppb.Timestamp{Seconds: 1553036601}, 1882 OptStruct: &structpb.Struct{ 1883 Fields: map[string]*structpb.Value{ 1884 "string": {Kind: &structpb.Value_StringValue{"hello"}}, 1885 }, 1886 }, 1887 OptList: &structpb.ListValue{ 1888 Values: []*structpb.Value{ 1889 {Kind: &structpb.Value_NullValue{}}, 1890 {Kind: &structpb.Value_StringValue{}}, 1891 {Kind: &structpb.Value_StructValue{}}, 1892 {Kind: &structpb.Value_ListValue{}}, 1893 }, 1894 }, 1895 OptValue: &structpb.Value{ 1896 Kind: &structpb.Value_StringValue{"world"}, 1897 }, 1898 OptEmpty: &emptypb.Empty{}, 1899 OptAny: &anypb.Any{ 1900 TypeUrl: "google.protobuf.Empty", 1901 }, 1902 OptFieldmask: &fieldmaskpb.FieldMask{ 1903 Paths: []string{"foo_bar", "bar_foo"}, 1904 }, 1905 }, 1906 want: `{ 1907 "optBool": false, 1908 "optInt32": 42, 1909 "optInt64": "42", 1910 "optUint32": 42, 1911 "optUint64": "42", 1912 "optFloat": 1.23, 1913 "optDouble": 3.1415, 1914 "optString": "hello", 1915 "optBytes": "aGVsbG8=", 1916 "optDuration": "123s", 1917 "optTimestamp": "2019-03-19T23:03:21Z", 1918 "optStruct": { 1919 "string": "hello" 1920 }, 1921 "optList": [ 1922 null, 1923 "", 1924 {}, 1925 [] 1926 ], 1927 "optValue": "world", 1928 "optEmpty": {}, 1929 "optAny": { 1930 "@type": "google.protobuf.Empty", 1931 "value": {} 1932 }, 1933 "optFieldmask": "fooBar,barFoo" 1934}`, 1935 }, { 1936 desc: "EmitUnpopulated: proto2 optional scalars", 1937 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 1938 input: &pb2.Scalars{}, 1939 want: `{ 1940 "optBool": null, 1941 "optInt32": null, 1942 "optInt64": null, 1943 "optUint32": null, 1944 "optUint64": null, 1945 "optSint32": null, 1946 "optSint64": null, 1947 "optFixed32": null, 1948 "optFixed64": null, 1949 "optSfixed32": null, 1950 "optSfixed64": null, 1951 "optFloat": null, 1952 "optDouble": null, 1953 "optBytes": null, 1954 "optString": null 1955}`, 1956 }, { 1957 desc: "EmitUnpopulated: proto3 scalars", 1958 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 1959 input: &pb3.Scalars{}, 1960 want: `{ 1961 "sBool": false, 1962 "sInt32": 0, 1963 "sInt64": "0", 1964 "sUint32": 0, 1965 "sUint64": "0", 1966 "sSint32": 0, 1967 "sSint64": "0", 1968 "sFixed32": 0, 1969 "sFixed64": "0", 1970 "sSfixed32": 0, 1971 "sSfixed64": "0", 1972 "sFloat": 0, 1973 "sDouble": 0, 1974 "sBytes": "", 1975 "sString": "" 1976}`, 1977 }, { 1978 desc: "EmitUnpopulated: proto2 enum", 1979 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 1980 input: &pb2.Enums{}, 1981 want: `{ 1982 "optEnum": null, 1983 "rptEnum": [], 1984 "optNestedEnum": null, 1985 "rptNestedEnum": [] 1986}`, 1987 }, { 1988 desc: "EmitUnpopulated: proto3 enum", 1989 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 1990 input: &pb3.Enums{}, 1991 want: `{ 1992 "sEnum": "ZERO", 1993 "sNestedEnum": "CERO" 1994}`, 1995 }, { 1996 desc: "EmitUnpopulated: proto2 message and group fields", 1997 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 1998 input: &pb2.Nests{}, 1999 want: `{ 2000 "optNested": null, 2001 "optgroup": null, 2002 "rptNested": [], 2003 "rptgroup": [] 2004}`, 2005 }, { 2006 desc: "EmitUnpopulated: proto3 message field", 2007 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2008 input: &pb3.Nests{}, 2009 want: `{ 2010 "sNested": null 2011}`, 2012 }, { 2013 desc: "EmitUnpopulated: proto2 empty message and group fields", 2014 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2015 input: &pb2.Nests{ 2016 OptNested: &pb2.Nested{}, 2017 Optgroup: &pb2.Nests_OptGroup{}, 2018 }, 2019 want: `{ 2020 "optNested": { 2021 "optString": null, 2022 "optNested": null 2023 }, 2024 "optgroup": { 2025 "optString": null, 2026 "optNested": null, 2027 "optnestedgroup": null 2028 }, 2029 "rptNested": [], 2030 "rptgroup": [] 2031}`, 2032 }, { 2033 desc: "EmitUnpopulated: proto3 empty message field", 2034 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2035 input: &pb3.Nests{ 2036 SNested: &pb3.Nested{}, 2037 }, 2038 want: `{ 2039 "sNested": { 2040 "sString": "", 2041 "sNested": null 2042 } 2043}`, 2044 }, { 2045 desc: "EmitUnpopulated: proto2 required fields", 2046 mo: protojson.MarshalOptions{ 2047 AllowPartial: true, 2048 EmitUnpopulated: true, 2049 }, 2050 input: &pb2.Requireds{}, 2051 want: `{ 2052 "reqBool": null, 2053 "reqSfixed64": null, 2054 "reqDouble": null, 2055 "reqString": null, 2056 "reqEnum": null, 2057 "reqNested": null 2058}`, 2059 }, { 2060 desc: "EmitUnpopulated: repeated fields", 2061 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2062 input: &pb2.Repeats{}, 2063 want: `{ 2064 "rptBool": [], 2065 "rptInt32": [], 2066 "rptInt64": [], 2067 "rptUint32": [], 2068 "rptUint64": [], 2069 "rptFloat": [], 2070 "rptDouble": [], 2071 "rptString": [], 2072 "rptBytes": [] 2073}`, 2074 }, { 2075 desc: "EmitUnpopulated: repeated containing empty message", 2076 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2077 input: &pb2.Nests{ 2078 RptNested: []*pb2.Nested{nil, {}}, 2079 }, 2080 want: `{ 2081 "optNested": null, 2082 "optgroup": null, 2083 "rptNested": [ 2084 { 2085 "optString": null, 2086 "optNested": null 2087 }, 2088 { 2089 "optString": null, 2090 "optNested": null 2091 } 2092 ], 2093 "rptgroup": [] 2094}`, 2095 }, { 2096 desc: "EmitUnpopulated: map fields", 2097 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2098 input: &pb3.Maps{}, 2099 want: `{ 2100 "int32ToStr": {}, 2101 "boolToUint32": {}, 2102 "uint64ToEnum": {}, 2103 "strToNested": {}, 2104 "strToOneofs": {} 2105}`, 2106 }, { 2107 desc: "EmitUnpopulated: map containing empty message", 2108 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2109 input: &pb3.Maps{ 2110 StrToNested: map[string]*pb3.Nested{ 2111 "nested": &pb3.Nested{}, 2112 }, 2113 StrToOneofs: map[string]*pb3.Oneofs{ 2114 "nested": &pb3.Oneofs{}, 2115 }, 2116 }, 2117 want: `{ 2118 "int32ToStr": {}, 2119 "boolToUint32": {}, 2120 "uint64ToEnum": {}, 2121 "strToNested": { 2122 "nested": { 2123 "sString": "", 2124 "sNested": null 2125 } 2126 }, 2127 "strToOneofs": { 2128 "nested": {} 2129 } 2130}`, 2131 }, { 2132 desc: "EmitUnpopulated: oneof fields", 2133 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2134 input: &pb3.Oneofs{}, 2135 want: `{}`, 2136 }, { 2137 desc: "EmitUnpopulated: extensions", 2138 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2139 input: func() proto.Message { 2140 m := &pb2.Extensions{} 2141 proto.SetExtension(m, pb2.E_OptExtNested, &pb2.Nested{}) 2142 proto.SetExtension(m, pb2.E_RptExtNested, []*pb2.Nested{ 2143 nil, 2144 {}, 2145 }) 2146 return m 2147 }(), 2148 want: `{ 2149 "optString": null, 2150 "optBool": null, 2151 "optInt32": null, 2152 "[pb2.opt_ext_nested]": { 2153 "optString": null, 2154 "optNested": null 2155 }, 2156 "[pb2.rpt_ext_nested]": [ 2157 { 2158 "optString": null, 2159 "optNested": null 2160 }, 2161 { 2162 "optString": null, 2163 "optNested": null 2164 } 2165 ] 2166}`, 2167 }, { 2168 desc: "EmitUnpopulated: with populated fields", 2169 mo: protojson.MarshalOptions{EmitUnpopulated: true}, 2170 input: &pb2.Scalars{ 2171 OptInt32: proto.Int32(0xff), 2172 OptUint32: proto.Uint32(47), 2173 OptSint32: proto.Int32(-1001), 2174 OptFixed32: proto.Uint32(32), 2175 OptSfixed32: proto.Int32(-32), 2176 OptFloat: proto.Float32(1.02), 2177 OptBytes: []byte("谷歌"), 2178 }, 2179 want: `{ 2180 "optBool": null, 2181 "optInt32": 255, 2182 "optInt64": null, 2183 "optUint32": 47, 2184 "optUint64": null, 2185 "optSint32": -1001, 2186 "optSint64": null, 2187 "optFixed32": 32, 2188 "optFixed64": null, 2189 "optSfixed32": -32, 2190 "optSfixed64": null, 2191 "optFloat": 1.02, 2192 "optDouble": null, 2193 "optBytes": "6LC35q2M", 2194 "optString": null 2195}`, 2196 }, { 2197 desc: "UseEnumNumbers in singular field", 2198 mo: protojson.MarshalOptions{UseEnumNumbers: true}, 2199 input: &pb2.Enums{ 2200 OptEnum: pb2.Enum_ONE.Enum(), 2201 OptNestedEnum: pb2.Enums_UNO.Enum(), 2202 }, 2203 want: `{ 2204 "optEnum": 1, 2205 "optNestedEnum": 1 2206}`, 2207 }, { 2208 desc: "UseEnumNumbers in repeated field", 2209 mo: protojson.MarshalOptions{UseEnumNumbers: true}, 2210 input: &pb2.Enums{ 2211 RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42}, 2212 RptNestedEnum: []pb2.Enums_NestedEnum{pb2.Enums_UNO, pb2.Enums_DOS, 47}, 2213 }, 2214 want: `{ 2215 "rptEnum": [ 2216 1, 2217 2, 2218 10, 2219 42 2220 ], 2221 "rptNestedEnum": [ 2222 1, 2223 2, 2224 47 2225 ] 2226}`, 2227 }, { 2228 desc: "UseEnumNumbers in map field", 2229 mo: protojson.MarshalOptions{UseEnumNumbers: true}, 2230 input: &pb3.Maps{ 2231 Uint64ToEnum: map[uint64]pb3.Enum{ 2232 1: pb3.Enum_ONE, 2233 2: pb3.Enum_TWO, 2234 10: pb3.Enum_TEN, 2235 47: 47, 2236 }, 2237 }, 2238 want: `{ 2239 "uint64ToEnum": { 2240 "1": 1, 2241 "2": 2, 2242 "10": 10, 2243 "47": 47 2244 } 2245}`, 2246 }, { 2247 desc: "UseProtoNames", 2248 mo: protojson.MarshalOptions{UseProtoNames: true}, 2249 input: &pb2.Nests{ 2250 OptNested: &pb2.Nested{}, 2251 Optgroup: &pb2.Nests_OptGroup{ 2252 OptString: proto.String("inside a group"), 2253 OptNested: &pb2.Nested{ 2254 OptString: proto.String("nested message inside a group"), 2255 }, 2256 Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{ 2257 OptFixed32: proto.Uint32(47), 2258 }, 2259 }, 2260 Rptgroup: []*pb2.Nests_RptGroup{ 2261 { 2262 RptString: []string{"hello", "world"}, 2263 }, 2264 }, 2265 }, 2266 want: `{ 2267 "opt_nested": {}, 2268 "OptGroup": { 2269 "opt_string": "inside a group", 2270 "opt_nested": { 2271 "opt_string": "nested message inside a group" 2272 }, 2273 "OptNestedGroup": { 2274 "opt_fixed32": 47 2275 } 2276 }, 2277 "RptGroup": [ 2278 { 2279 "rpt_string": [ 2280 "hello", 2281 "world" 2282 ] 2283 } 2284 ] 2285}`, 2286 }} 2287 2288 for _, tt := range tests { 2289 tt := tt 2290 if tt.skip { 2291 continue 2292 } 2293 t.Run(tt.desc, func(t *testing.T) { 2294 // Use 2-space indentation on all MarshalOptions. 2295 tt.mo.Indent = " " 2296 b, err := tt.mo.Marshal(tt.input) 2297 if err != nil && !tt.wantErr { 2298 t.Errorf("Marshal() returned error: %v\n", err) 2299 } 2300 if err == nil && tt.wantErr { 2301 t.Errorf("Marshal() got nil error, want error\n") 2302 } 2303 got := string(b) 2304 if got != tt.want { 2305 t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want) 2306 if diff := cmp.Diff(tt.want, got); diff != "" { 2307 t.Errorf("Marshal() diff -want +got\n%v\n", diff) 2308 } 2309 } 2310 }) 2311 } 2312} 2313