encoding/protojson: add MarshalOptions.UseEnumNumbers

UseEnumNumbers=true will emit enum values as JSON numbers.

Change-Id: I6f3c814e06dc1e3dd595ad35aa79871a49718cd5
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/194017
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/encoding/protojson/encode.go b/encoding/protojson/encode.go
index c761fec..d55786e 100644
--- a/encoding/protojson/encode.go
+++ b/encoding/protojson/encode.go
@@ -34,6 +34,9 @@
 	// Marshal will return error if there are any missing required fields.
 	AllowPartial bool
 
+	// UseEnumNumbers emits enum values as numbers.
+	UseEnumNumbers bool
+
 	// EmitUnpopulated specifies whether to emit unpopulated fields. It does not
 	// emit unpopulated oneof fields or unpopulated extension fields.
 	// The JSON value emitted for unpopulated fields are as follows:
@@ -197,14 +200,16 @@
 	case pref.EnumKind:
 		if fd.Enum().FullName() == "google.protobuf.NullValue" {
 			o.encoder.WriteNull()
-		} else if desc := fd.Enum().Values().ByNumber(val.Enum()); desc != nil {
-			err := o.encoder.WriteString(string(desc.Name()))
-			if err != nil {
-				return err
-			}
 		} else {
-			// Use numeric value if there is no enum value descriptor.
-			o.encoder.WriteInt(int64(val.Enum()))
+			desc := fd.Enum().Values().ByNumber(val.Enum())
+			if o.UseEnumNumbers || desc == nil {
+				o.encoder.WriteInt(int64(val.Enum()))
+			} else {
+				err := o.encoder.WriteString(string(desc.Name()))
+				if err != nil {
+					return err
+				}
+			}
 		}
 
 	case pref.MessageKind, pref.GroupKind:
diff --git a/encoding/protojson/encode_test.go b/encoding/protojson/encode_test.go
index 661ee59..4ee275a 100644
--- a/encoding/protojson/encode_test.go
+++ b/encoding/protojson/encode_test.go
@@ -2125,6 +2125,56 @@
     }
   ]
 }`,
+	}, {
+		desc: "UseEnumNumbers in singular field",
+		mo:   protojson.MarshalOptions{UseEnumNumbers: true},
+		input: &pb2.Enums{
+			OptEnum:       pb2.Enum_ONE.Enum(),
+			OptNestedEnum: pb2.Enums_UNO.Enum(),
+		},
+		want: `{
+  "optEnum": 1,
+  "optNestedEnum": 1
+}`,
+	}, {
+		desc: "UseEnumNumbers in repeated field",
+		mo:   protojson.MarshalOptions{UseEnumNumbers: true},
+		input: &pb2.Enums{
+			RptEnum:       []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
+			RptNestedEnum: []pb2.Enums_NestedEnum{pb2.Enums_UNO, pb2.Enums_DOS, 47},
+		},
+		want: `{
+  "rptEnum": [
+    1,
+    2,
+    10,
+    42
+  ],
+  "rptNestedEnum": [
+    1,
+    2,
+    47
+  ]
+}`,
+	}, {
+		desc: "UseEnumNumbers in map field",
+		mo:   protojson.MarshalOptions{UseEnumNumbers: true},
+		input: &pb3.Maps{
+			Uint64ToEnum: map[uint64]pb3.Enum{
+				1:  pb3.Enum_ONE,
+				2:  pb3.Enum_TWO,
+				10: pb3.Enum_TEN,
+				47: 47,
+			},
+		},
+		want: `{
+  "uint64ToEnum": {
+    "1": 1,
+    "2": 2,
+    "10": 10,
+    "47": 47
+  }
+}`,
 	}}
 
 	for _, tt := range tests {