encoding/jsonpb: basic JSON marshaling

This does not handle extensions, messagesets, and well-known types yet.

Change-Id: I2786c429f490fe8c57f3f85cd25058d936b58bf7
Reviewed-on: https://go-review.googlesource.com/c/162637
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/encoding/jsonpb/encode.go b/encoding/jsonpb/encode.go
new file mode 100644
index 0000000..928b24f
--- /dev/null
+++ b/encoding/jsonpb/encode.go
@@ -0,0 +1,231 @@
+// 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 jsonpb
+
+import (
+	"encoding/base64"
+	"math"
+	"sort"
+
+	"github.com/golang/protobuf/v2/internal/encoding/json"
+	"github.com/golang/protobuf/v2/internal/errors"
+	"github.com/golang/protobuf/v2/internal/pragma"
+	"github.com/golang/protobuf/v2/proto"
+	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
+)
+
+// Marshal writes the given proto.Message in JSON format using default options.
+func Marshal(m proto.Message) ([]byte, error) {
+	return MarshalOptions{}.Marshal(m)
+}
+
+// MarshalOptions is a configurable JSON format marshaler.
+type MarshalOptions struct {
+	pragma.NoUnkeyedLiterals
+
+	// Set Compact to true to have output in a single line with no line breaks.
+	Compact bool
+}
+
+// Marshal writes the given proto.Message in JSON format using options in MarshalOptions object.
+func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
+	var nerr errors.NonFatal
+	v, err := o.marshalMessage(m.ProtoReflect())
+	if !nerr.Merge(err) {
+		return nil, err
+	}
+
+	indent := "  "
+	if o.Compact {
+		indent = ""
+	}
+
+	b, err := json.Marshal(v, indent)
+	if !nerr.Merge(err) {
+		return nil, err
+	}
+	return b, nerr.E
+}
+
+// marshalMessage converts a protoreflect.Message to a json.Value.
+func (o MarshalOptions) marshalMessage(m pref.Message) (json.Value, error) {
+	var nerr errors.NonFatal
+	var msgFields [][2]json.Value
+
+	msgType := m.Type()
+	fieldDescs := msgType.Fields()
+	knownFields := m.KnownFields()
+	size := fieldDescs.Len()
+	for i := 0; i < size; i++ {
+		fd := fieldDescs.Get(i)
+		num := fd.Number()
+
+		if !knownFields.Has(num) {
+			if fd.Cardinality() == pref.Required {
+				// Treat unset required fields as a non-fatal error.
+				nerr.AppendRequiredNotSet(string(fd.FullName()))
+			}
+			continue
+		}
+
+		name := json.ValueOf(fd.JSONName())
+		pval := knownFields.Get(num)
+		var err error
+		msgFields, err = o.appendField(msgFields, name, pval, fd)
+		if !nerr.Merge(err) {
+			return json.Value{}, err
+		}
+	}
+
+	return json.ValueOf(msgFields), nerr.E
+}
+
+// appendField marshals a protoreflect.Value and appends it to the given
+// [][2]json.Value.
+func (o MarshalOptions) appendField(msgFields [][2]json.Value, name json.Value, pval pref.Value, fd pref.FieldDescriptor) ([][2]json.Value, error) {
+	var nerr errors.NonFatal
+	var jval json.Value
+	var err error
+
+	if fd.Cardinality() == pref.Repeated {
+		// Map or repeated fields.
+		if fd.IsMap() {
+			jval, err = o.marshalMap(pval.Map(), fd)
+			if !nerr.Merge(err) {
+				return msgFields, err
+			}
+		} else {
+			jval, err = o.marshalList(pval.List(), fd)
+			if !nerr.Merge(err) {
+				return msgFields, err
+			}
+		}
+	} else {
+		// Required or optional fields.
+		jval, err = o.marshalSingular(pval, fd)
+		if !nerr.Merge(err) {
+			return msgFields, err
+		}
+	}
+
+	msgFields = append(msgFields, [2]json.Value{name, jval})
+	return msgFields, nerr.E
+}
+
+// marshalSingular converts a non-repeated field value to json.Value.
+// This includes all scalar types, enums, messages, and groups.
+func (o MarshalOptions) marshalSingular(val pref.Value, fd pref.FieldDescriptor) (json.Value, error) {
+	kind := fd.Kind()
+	switch kind {
+	case pref.BoolKind, pref.StringKind,
+		pref.Int32Kind, pref.Sint32Kind, pref.Uint32Kind,
+		pref.Sfixed32Kind, pref.Fixed32Kind:
+		return json.ValueOf(val.Interface()), nil
+
+	case pref.Int64Kind, pref.Sint64Kind, pref.Uint64Kind,
+		pref.Sfixed64Kind, pref.Fixed64Kind:
+		return json.ValueOf(val.String()), nil
+
+	case pref.FloatKind, pref.DoubleKind:
+		n := val.Float()
+		switch {
+		case math.IsNaN(n):
+			return json.ValueOf("NaN"), nil
+		case math.IsInf(n, +1):
+			return json.ValueOf("Infinity"), nil
+		case math.IsInf(n, -1):
+			return json.ValueOf("-Infinity"), nil
+		default:
+			return json.ValueOf(n), nil
+		}
+
+	case pref.BytesKind:
+		return json.ValueOf(base64.StdEncoding.EncodeToString(val.Bytes())), nil
+
+	case pref.EnumKind:
+		num := val.Enum()
+		if desc := fd.EnumType().Values().ByNumber(num); desc != nil {
+			return json.ValueOf(string(desc.Name())), nil
+		}
+		// Use numeric value if there is no enum value descriptor.
+		return json.ValueOf(int32(num)), nil
+
+	case pref.MessageKind, pref.GroupKind:
+		return o.marshalMessage(val.Message())
+	}
+
+	return json.Value{}, errors.New("%v has unknown kind: %v", fd.FullName(), kind)
+}
+
+// marshalList converts a protoreflect.List to json.Value.
+func (o MarshalOptions) marshalList(list pref.List, fd pref.FieldDescriptor) (json.Value, error) {
+	var nerr errors.NonFatal
+	size := list.Len()
+	values := make([]json.Value, 0, size)
+
+	for i := 0; i < size; i++ {
+		item := list.Get(i)
+		val, err := o.marshalSingular(item, fd)
+		if !nerr.Merge(err) {
+			return json.Value{}, err
+		}
+		values = append(values, val)
+	}
+
+	return json.ValueOf(values), nerr.E
+}
+
+type mapEntry struct {
+	key   pref.MapKey
+	value pref.Value
+}
+
+// marshalMap converts a protoreflect.Map to json.Value.
+func (o MarshalOptions) marshalMap(mmap pref.Map, fd pref.FieldDescriptor) (json.Value, error) {
+	msgFields := fd.MessageType().Fields()
+	keyType := msgFields.ByNumber(1)
+	valType := msgFields.ByNumber(2)
+
+	// Get a sorted list based on keyType first.
+	entries := make([]mapEntry, 0, mmap.Len())
+	mmap.Range(func(key pref.MapKey, val pref.Value) bool {
+		entries = append(entries, mapEntry{key: key, value: val})
+		return true
+	})
+	sortMap(keyType.Kind(), entries)
+
+	// Convert to list of [2]json.Value.
+	var nerr errors.NonFatal
+	values := make([][2]json.Value, 0, len(entries))
+	for _, entry := range entries {
+		jkey := json.ValueOf(entry.key.String())
+		jval, err := o.marshalSingular(entry.value, valType)
+		if !nerr.Merge(err) {
+			return json.Value{}, err
+		}
+		values = append(values, [2]json.Value{jkey, jval})
+	}
+	return json.ValueOf(values), nerr.E
+}
+
+// sortMap orders list based on value of key field for deterministic output.
+func sortMap(keyKind pref.Kind, values []mapEntry) {
+	less := func(i, j int) bool {
+		return values[i].key.String() < values[j].key.String()
+	}
+	switch keyKind {
+	case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind,
+		pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
+		less = func(i, j int) bool {
+			return values[i].key.Int() < values[j].key.Int()
+		}
+	case pref.Uint32Kind, pref.Fixed32Kind,
+		pref.Uint64Kind, pref.Fixed64Kind:
+		less = func(i, j int) bool {
+			return values[i].key.Uint() < values[j].key.Uint()
+		}
+	}
+	sort.Slice(values, less)
+}
diff --git a/encoding/jsonpb/encode_test.go b/encoding/jsonpb/encode_test.go
new file mode 100644
index 0000000..5ee09b2
--- /dev/null
+++ b/encoding/jsonpb/encode_test.go
@@ -0,0 +1,727 @@
+// 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 jsonpb_test
+
+import (
+	"math"
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/v2/encoding/jsonpb"
+	"github.com/golang/protobuf/v2/internal/encoding/pack"
+	"github.com/golang/protobuf/v2/internal/scalar"
+	"github.com/golang/protobuf/v2/proto"
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+
+	// The legacy package must be imported prior to use of any legacy messages.
+	// TODO: Remove this when protoV1 registers these hooks for you.
+	_ "github.com/golang/protobuf/v2/internal/legacy"
+
+	"github.com/golang/protobuf/v2/encoding/testprotos/pb2"
+	"github.com/golang/protobuf/v2/encoding/testprotos/pb3"
+)
+
+// 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 pb2Enum(i int32) *pb2.Enum {
+	p := new(pb2.Enum)
+	*p = pb2.Enum(i)
+	return p
+}
+
+func pb2Enums_NestedEnum(i int32) *pb2.Enums_NestedEnum {
+	p := new(pb2.Enums_NestedEnum)
+	*p = pb2.Enums_NestedEnum(i)
+	return p
+}
+
+func TestMarshal(t *testing.T) {
+	tests := []struct {
+		desc  string
+		mo    jsonpb.MarshalOptions
+		input proto.Message
+		want  string
+	}{{
+		desc:  "proto2 optional scalars not set",
+		input: &pb2.Scalars{},
+		want:  "{}",
+	}, {
+		desc:  "proto3 scalars not set",
+		input: &pb3.Scalars{},
+		want:  "{}",
+	}, {
+		desc: "proto2 optional scalars set to zero values",
+		input: &pb2.Scalars{
+			OptBool:     scalar.Bool(false),
+			OptInt32:    scalar.Int32(0),
+			OptInt64:    scalar.Int64(0),
+			OptUint32:   scalar.Uint32(0),
+			OptUint64:   scalar.Uint64(0),
+			OptSint32:   scalar.Int32(0),
+			OptSint64:   scalar.Int64(0),
+			OptFixed32:  scalar.Uint32(0),
+			OptFixed64:  scalar.Uint64(0),
+			OptSfixed32: scalar.Int32(0),
+			OptSfixed64: scalar.Int64(0),
+			OptFloat:    scalar.Float32(0),
+			OptDouble:   scalar.Float64(0),
+			OptBytes:    []byte{},
+			OptString:   scalar.String(""),
+		},
+		want: `{
+  "optBool": false,
+  "optInt32": 0,
+  "optInt64": "0",
+  "optUint32": 0,
+  "optUint64": "0",
+  "optSint32": 0,
+  "optSint64": "0",
+  "optFixed32": 0,
+  "optFixed64": "0",
+  "optSfixed32": 0,
+  "optSfixed64": "0",
+  "optFloat": 0,
+  "optDouble": 0,
+  "optBytes": "",
+  "optString": ""
+}`,
+	}, {
+		desc: "proto2 optional scalars set to some values",
+		input: &pb2.Scalars{
+			OptBool:     scalar.Bool(true),
+			OptInt32:    scalar.Int32(0xff),
+			OptInt64:    scalar.Int64(0xdeadbeef),
+			OptUint32:   scalar.Uint32(47),
+			OptUint64:   scalar.Uint64(0xdeadbeef),
+			OptSint32:   scalar.Int32(-1001),
+			OptSint64:   scalar.Int64(-0xffff),
+			OptFixed64:  scalar.Uint64(64),
+			OptSfixed32: scalar.Int32(-32),
+			OptFloat:    scalar.Float32(1.02),
+			OptDouble:   scalar.Float64(1.234),
+			OptBytes:    []byte("\xe8\xb0\xb7\xe6\xad\x8c"),
+			OptString:   scalar.String("谷歌"),
+		},
+		want: `{
+  "optBool": true,
+  "optInt32": 255,
+  "optInt64": "3735928559",
+  "optUint32": 47,
+  "optUint64": "3735928559",
+  "optSint32": -1001,
+  "optSint64": "-65535",
+  "optFixed64": "64",
+  "optSfixed32": -32,
+  "optFloat": 1.02,
+  "optDouble": 1.234,
+  "optBytes": "6LC35q2M",
+  "optString": "谷歌"
+}`,
+	}, {
+		desc: "float nan",
+		input: &pb3.Scalars{
+			SFloat: float32(math.NaN()),
+		},
+		want: `{
+  "sFloat": "NaN"
+}`,
+	}, {
+		desc: "float positive infinity",
+		input: &pb3.Scalars{
+			SFloat: float32(math.Inf(1)),
+		},
+		want: `{
+  "sFloat": "Infinity"
+}`,
+	}, {
+		desc: "float negative infinity",
+		input: &pb3.Scalars{
+			SFloat: float32(math.Inf(-1)),
+		},
+		want: `{
+  "sFloat": "-Infinity"
+}`,
+	}, {
+		desc: "double nan",
+		input: &pb3.Scalars{
+			SDouble: math.NaN(),
+		},
+		want: `{
+  "sDouble": "NaN"
+}`,
+	}, {
+		desc: "double positive infinity",
+		input: &pb3.Scalars{
+			SDouble: math.Inf(1),
+		},
+		want: `{
+  "sDouble": "Infinity"
+}`,
+	}, {
+		desc: "double negative infinity",
+		input: &pb3.Scalars{
+			SDouble: math.Inf(-1),
+		},
+		want: `{
+  "sDouble": "-Infinity"
+}`,
+	}, {
+		desc:  "proto2 enum not set",
+		input: &pb2.Enums{},
+		want:  "{}",
+	}, {
+		desc: "proto2 enum set to zero value",
+		input: &pb2.Enums{
+			OptEnum:       pb2Enum(0),
+			OptNestedEnum: pb2Enums_NestedEnum(0),
+		},
+		want: `{
+  "optEnum": 0,
+  "optNestedEnum": 0
+}`,
+	}, {
+		desc: "proto2 enum",
+		input: &pb2.Enums{
+			OptEnum:       pb2.Enum_ONE.Enum(),
+			OptNestedEnum: pb2.Enums_UNO.Enum(),
+		},
+		want: `{
+  "optEnum": "ONE",
+  "optNestedEnum": "UNO"
+}`,
+	}, {
+		desc: "proto2 enum set to numeric values",
+		input: &pb2.Enums{
+			OptEnum:       pb2Enum(2),
+			OptNestedEnum: pb2Enums_NestedEnum(2),
+		},
+		want: `{
+  "optEnum": "TWO",
+  "optNestedEnum": "DOS"
+}`,
+	}, {
+		desc: "proto2 enum set to unnamed numeric values",
+		input: &pb2.Enums{
+			OptEnum:       pb2Enum(101),
+			OptNestedEnum: pb2Enums_NestedEnum(-101),
+		},
+		want: `{
+  "optEnum": 101,
+  "optNestedEnum": -101
+}`,
+	}, {
+		desc:  "proto3 enum not set",
+		input: &pb3.Enums{},
+		want:  "{}",
+	}, {
+		desc: "proto3 enum set to zero value",
+		input: &pb3.Enums{
+			SEnum:       pb3.Enum_ZERO,
+			SNestedEnum: pb3.Enums_CERO,
+		},
+		want: "{}",
+	}, {
+		desc: "proto3 enum",
+		input: &pb3.Enums{
+			SEnum:       pb3.Enum_ONE,
+			SNestedEnum: pb3.Enums_UNO,
+		},
+		want: `{
+  "sEnum": "ONE",
+  "sNestedEnum": "UNO"
+}`,
+	}, {
+		desc: "proto3 enum set to numeric values",
+		input: &pb3.Enums{
+			SEnum:       2,
+			SNestedEnum: 2,
+		},
+		want: `{
+  "sEnum": "TWO",
+  "sNestedEnum": "DOS"
+}`,
+	}, {
+		desc: "proto3 enum set to unnamed numeric values",
+		input: &pb3.Enums{
+			SEnum:       -47,
+			SNestedEnum: 47,
+		},
+		want: `{
+  "sEnum": -47,
+  "sNestedEnum": 47
+}`,
+	}, {
+		desc:  "proto2 nested message not set",
+		input: &pb2.Nests{},
+		want:  "{}",
+	}, {
+		desc: "proto2 nested message set to empty",
+		input: &pb2.Nests{
+			OptNested: &pb2.Nested{},
+			Optgroup:  &pb2.Nests_OptGroup{},
+		},
+		want: `{
+  "optNested": {},
+  "optgroup": {}
+}`,
+	}, {
+		desc: "proto2 nested messages",
+		input: &pb2.Nests{
+			OptNested: &pb2.Nested{
+				OptString: scalar.String("nested message"),
+				OptNested: &pb2.Nested{
+					OptString: scalar.String("another nested message"),
+				},
+			},
+		},
+		want: `{
+  "optNested": {
+    "optString": "nested message",
+    "optNested": {
+      "optString": "another nested message"
+    }
+  }
+}`,
+	}, {
+		desc: "proto2 groups",
+		input: &pb2.Nests{
+			Optgroup: &pb2.Nests_OptGroup{
+				OptString: scalar.String("inside a group"),
+				OptNested: &pb2.Nested{
+					OptString: scalar.String("nested message inside a group"),
+				},
+				Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{
+					OptFixed32: scalar.Uint32(47),
+				},
+			},
+		},
+		want: `{
+  "optgroup": {
+    "optString": "inside a group",
+    "optNested": {
+      "optString": "nested message inside a group"
+    },
+    "optnestedgroup": {
+      "optFixed32": 47
+    }
+  }
+}`,
+	}, {
+		desc:  "proto3 nested message not set",
+		input: &pb3.Nests{},
+		want:  "{}",
+	}, {
+		desc: "proto3 nested message set to empty",
+		input: &pb3.Nests{
+			SNested: &pb3.Nested{},
+		},
+		want: `{
+  "sNested": {}
+}`,
+	}, {
+		desc: "proto3 nested message",
+		input: &pb3.Nests{
+			SNested: &pb3.Nested{
+				SString: "nested message",
+				SNested: &pb3.Nested{
+					SString: "another nested message",
+				},
+			},
+		},
+		want: `{
+  "sNested": {
+    "sString": "nested message",
+    "sNested": {
+      "sString": "another nested message"
+    }
+  }
+}`,
+	}, {
+		desc:  "oneof not set",
+		input: &pb3.Oneofs{},
+		want:  "{}",
+	}, {
+		desc: "oneof set to empty string",
+		input: &pb3.Oneofs{
+			Union: &pb3.Oneofs_OneofString{},
+		},
+		want: `{
+  "oneofString": ""
+}`,
+	}, {
+		desc: "oneof set to string",
+		input: &pb3.Oneofs{
+			Union: &pb3.Oneofs_OneofString{
+				OneofString: "hello",
+			},
+		},
+		want: `{
+  "oneofString": "hello"
+}`,
+	}, {
+		desc: "oneof set to enum",
+		input: &pb3.Oneofs{
+			Union: &pb3.Oneofs_OneofEnum{
+				OneofEnum: pb3.Enum_ZERO,
+			},
+		},
+		want: `{
+  "oneofEnum": "ZERO"
+}`,
+	}, {
+		desc: "oneof set to empty message",
+		input: &pb3.Oneofs{
+			Union: &pb3.Oneofs_OneofNested{
+				OneofNested: &pb3.Nested{},
+			},
+		},
+		want: `{
+  "oneofNested": {}
+}`,
+	}, {
+		desc: "oneof set to message",
+		input: &pb3.Oneofs{
+			Union: &pb3.Oneofs_OneofNested{
+				OneofNested: &pb3.Nested{
+					SString: "nested message",
+				},
+			},
+		},
+		want: `{
+  "oneofNested": {
+    "sString": "nested message"
+  }
+}`,
+	}, {
+		desc:  "repeated fields not set",
+		input: &pb2.Repeats{},
+		want:  "{}",
+	}, {
+		desc: "repeated fields set to empty slices",
+		input: &pb2.Repeats{
+			RptBool:   []bool{},
+			RptInt32:  []int32{},
+			RptInt64:  []int64{},
+			RptUint32: []uint32{},
+			RptUint64: []uint64{},
+			RptFloat:  []float32{},
+			RptDouble: []float64{},
+			RptBytes:  [][]byte{},
+		},
+		want: "{}",
+	}, {
+		desc: "repeated fields set to some values",
+		input: &pb2.Repeats{
+			RptBool:   []bool{true, false, true, true},
+			RptInt32:  []int32{1, 6, 0, 0},
+			RptInt64:  []int64{-64, 47},
+			RptUint32: []uint32{0xff, 0xffff},
+			RptUint64: []uint64{0xdeadbeef},
+			RptFloat:  []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034},
+			RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308},
+			RptString: []string{"hello", "世界"},
+			RptBytes: [][]byte{
+				[]byte("hello"),
+				[]byte("\xe4\xb8\x96\xe7\x95\x8c"),
+			},
+		},
+		want: `{
+  "rptBool": [
+    true,
+    false,
+    true,
+    true
+  ],
+  "rptInt32": [
+    1,
+    6,
+    0,
+    0
+  ],
+  "rptInt64": [
+    "-64",
+    "47"
+  ],
+  "rptUint32": [
+    255,
+    65535
+  ],
+  "rptUint64": [
+    "3735928559"
+  ],
+  "rptFloat": [
+    "NaN",
+    "Infinity",
+    "-Infinity",
+    1.034
+  ],
+  "rptDouble": [
+    "NaN",
+    "Infinity",
+    "-Infinity",
+    1.23e-308
+  ],
+  "rptString": [
+    "hello",
+    "世界"
+  ],
+  "rptBytes": [
+    "aGVsbG8=",
+    "5LiW55WM"
+  ]
+}`,
+	}, {
+		desc: "repeated enums",
+		input: &pb2.Enums{
+			RptEnum:       []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
+			RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10},
+		},
+		want: `{
+  "rptEnum": [
+    "ONE",
+    "TWO",
+    "TEN",
+    42
+  ],
+  "rptNestedEnum": [
+    "DOS",
+    47,
+    "DIEZ"
+  ]
+}`,
+	}, {
+		desc: "repeated messages set to empty",
+		input: &pb2.Nests{
+			RptNested: []*pb2.Nested{},
+			Rptgroup:  []*pb2.Nests_RptGroup{},
+		},
+		want: "{}",
+	}, {
+		desc: "repeated messages",
+		input: &pb2.Nests{
+			RptNested: []*pb2.Nested{
+				{
+					OptString: scalar.String("repeat nested one"),
+				},
+				{
+					OptString: scalar.String("repeat nested two"),
+					OptNested: &pb2.Nested{
+						OptString: scalar.String("inside repeat nested two"),
+					},
+				},
+				{},
+			},
+		},
+		want: `{
+  "rptNested": [
+    {
+      "optString": "repeat nested one"
+    },
+    {
+      "optString": "repeat nested two",
+      "optNested": {
+        "optString": "inside repeat nested two"
+      }
+    },
+    {}
+  ]
+}`,
+	}, {
+		desc: "repeated messages contains nil value",
+		input: &pb2.Nests{
+			RptNested: []*pb2.Nested{nil, {}},
+		},
+		want: `{
+  "rptNested": [
+    {},
+    {}
+  ]
+}`,
+	}, {
+		desc: "repeated groups",
+		input: &pb2.Nests{
+			Rptgroup: []*pb2.Nests_RptGroup{
+				{
+					RptString: []string{"hello", "world"},
+				},
+				{},
+				nil,
+			},
+		},
+		want: `{
+  "rptgroup": [
+    {
+      "rptString": [
+        "hello",
+        "world"
+      ]
+    },
+    {},
+    {}
+  ]
+}`,
+	}, {
+		desc:  "map fields not set",
+		input: &pb3.Maps{},
+		want:  "{}",
+	}, {
+		desc: "map fields set to empty",
+		input: &pb3.Maps{
+			Int32ToStr:   map[int32]string{},
+			BoolToUint32: map[bool]uint32{},
+			Uint64ToEnum: map[uint64]pb3.Enum{},
+			StrToNested:  map[string]*pb3.Nested{},
+			StrToOneofs:  map[string]*pb3.Oneofs{},
+		},
+		want: "{}",
+	}, {
+		desc: "map fields 1",
+		input: &pb3.Maps{
+			BoolToUint32: map[bool]uint32{
+				true:  42,
+				false: 101,
+			},
+		},
+		want: `{
+  "boolToUint32": {
+    "false": 101,
+    "true": 42
+  }
+}`,
+	}, {
+		desc: "map fields 2",
+		input: &pb3.Maps{
+			Int32ToStr: map[int32]string{
+				-101: "-101",
+				0xff: "0xff",
+				0:    "zero",
+			},
+		},
+		want: `{
+  "int32ToStr": {
+    "-101": "-101",
+    "0": "zero",
+    "255": "0xff"
+  }
+}`,
+	}, {
+		desc: "map fields 3",
+		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": "ONE",
+    "2": "TWO",
+    "10": "TEN",
+    "47": 47
+  }
+}`,
+	}, {
+		desc: "map fields 4",
+		input: &pb3.Maps{
+			StrToNested: map[string]*pb3.Nested{
+				"nested": &pb3.Nested{
+					SString: "nested in a map",
+				},
+			},
+		},
+		want: `{
+  "strToNested": {
+    "nested": {
+      "sString": "nested in a map"
+    }
+  }
+}`,
+	}, {
+		desc: "map fields 5",
+		input: &pb3.Maps{
+			StrToOneofs: map[string]*pb3.Oneofs{
+				"string": &pb3.Oneofs{
+					Union: &pb3.Oneofs_OneofString{
+						OneofString: "hello",
+					},
+				},
+				"nested": &pb3.Oneofs{
+					Union: &pb3.Oneofs_OneofNested{
+						OneofNested: &pb3.Nested{
+							SString: "nested oneof in map field value",
+						},
+					},
+				},
+			},
+		},
+		want: `{
+  "strToOneofs": {
+    "nested": {
+      "oneofNested": {
+        "sString": "nested oneof in map field value"
+      }
+    },
+    "string": {
+      "oneofString": "hello"
+    }
+  }
+}`,
+	}, {
+		desc: "map field contains nil value",
+		input: &pb3.Maps{
+			StrToNested: map[string]*pb3.Nested{
+				"nil": nil,
+			},
+		},
+		want: `{
+  "strToNested": {
+    "nil": {}
+  }
+}`,
+	}, {
+		desc: "unknown fields are ignored",
+		input: &pb2.Scalars{
+			OptString: scalar.String("no unknowns"),
+			XXX_unrecognized: pack.Message{
+				pack.Tag{101, pack.BytesType}, pack.String("hello world"),
+			}.Marshal(),
+		},
+		want: `{
+  "optString": "no unknowns"
+}`,
+	}, {
+		desc: "json_name",
+		input: &pb3.JSONNames{
+			SString: "json_name",
+		},
+		want: `{
+  "foo_bar": "json_name"
+}`,
+	}}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.desc, func(t *testing.T) {
+			t.Parallel()
+			b, err := tt.mo.Marshal(tt.input)
+			if err != nil {
+				t.Errorf("Marshal() returned error: %v\n", err)
+			}
+			got := string(b)
+			if got != tt.want {
+				t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want)
+				if diff := cmp.Diff(tt.want, got, splitLines); diff != "" {
+					t.Errorf("Marshal() diff -want +got\n%v\n", diff)
+				}
+			}
+		})
+	}
+}
diff --git a/encoding/testprotos/pb3/test.pb.go b/encoding/testprotos/pb3/test.pb.go
index 54209a9..0aabdf1 100644
--- a/encoding/testprotos/pb3/test.pb.go
+++ b/encoding/testprotos/pb3/test.pb.go
@@ -568,6 +568,49 @@
 	return nil
 }
 
+// Message for testing json_name option.
+type JSONNames struct {
+	SString              string   `protobuf:"bytes,1,opt,name=s_string,json=foo_bar,proto3" json:"s_string,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *JSONNames) ProtoReflect() protoreflect.Message {
+	return xxx_Test_protoFile_messageTypes[6].MessageOf(m)
+}
+func (m *JSONNames) Reset()         { *m = JSONNames{} }
+func (m *JSONNames) String() string { return proto.CompactTextString(m) }
+func (*JSONNames) ProtoMessage()    {}
+func (*JSONNames) Descriptor() ([]byte, []int) {
+	return fileDescriptor_33e0a17922cde063_gzipped, []int{6}
+}
+
+func (m *JSONNames) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_JSONNames.Unmarshal(m, b)
+}
+func (m *JSONNames) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_JSONNames.Marshal(b, m, deterministic)
+}
+func (m *JSONNames) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_JSONNames.Merge(m, src)
+}
+func (m *JSONNames) XXX_Size() int {
+	return xxx_messageInfo_JSONNames.Size(m)
+}
+func (m *JSONNames) XXX_DiscardUnknown() {
+	xxx_messageInfo_JSONNames.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_JSONNames proto.InternalMessageInfo
+
+func (m *JSONNames) GetSString() string {
+	if m != nil {
+		return m.SString
+	}
+	return ""
+}
+
 func init() {
 	proto.RegisterFile("encoding/testprotos/pb3/test.proto", fileDescriptor_33e0a17922cde063_gzipped)
 	proto.RegisterEnum("pb3.Enum", Enum_name, Enum_value)
@@ -583,10 +626,11 @@
 	proto.RegisterMapType((map[string]*Nested)(nil), "pb3.Maps.StrToNestedEntry")
 	proto.RegisterMapType((map[string]*Oneofs)(nil), "pb3.Maps.StrToOneofsEntry")
 	proto.RegisterMapType((map[uint64]Enum)(nil), "pb3.Maps.Uint64ToEnumEntry")
+	proto.RegisterType((*JSONNames)(nil), "pb3.JSONNames")
 }
 
 var fileDescriptor_33e0a17922cde063 = []byte{
-	// 1690 bytes of the wire-encoded FileDescriptorProto
+	// 1730 bytes of the wire-encoded FileDescriptorProto
 	0x0a, 0x22, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70,
 	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x62, 0x33, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70,
 	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x70, 0x62, 0x33, 0x22, 0x9e, 0x03, 0x0a, 0x07, 0x53, 0x63,
@@ -685,14 +729,17 @@
 	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
 	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
 	0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x62, 0x33, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66,
-	0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x2b, 0x0a, 0x04,
-	0x45, 0x6e, 0x75, 0x6d, 0x12, 0x08, 0x0a, 0x04, 0x5a, 0x45, 0x52, 0x4f, 0x10, 0x00, 0x12, 0x07,
-	0x0a, 0x03, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x57, 0x4f, 0x10, 0x02,
-	0x12, 0x07, 0x0a, 0x03, 0x54, 0x45, 0x4e, 0x10, 0x0a, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74,
-	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x6e, 0x63, 0x6f, 0x64,
-	0x69, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-	0x62, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x26, 0x0a, 0x09,
+	0x4a, 0x53, 0x4f, 0x4e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x5f, 0x73,
+	0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6f, 0x6f,
+	0x5f, 0x62, 0x61, 0x72, 0x2a, 0x2b, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x08, 0x0a, 0x04,
+	0x5a, 0x45, 0x52, 0x4f, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12,
+	0x07, 0x0a, 0x03, 0x54, 0x57, 0x4f, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x45, 0x4e, 0x10,
+	0x0a, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+	0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
+	0x76, 0x32, 0x2f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x62, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var fileDescriptor_33e0a17922cde063_gzipped = func() []byte {
@@ -708,7 +755,7 @@
 var Test_protoFile protoreflect.FileDescriptor
 
 var xxx_Test_protoFile_enumTypes [2]protoreflect.EnumType
-var xxx_Test_protoFile_messageTypes [11]protoimpl.MessageType
+var xxx_Test_protoFile_messageTypes [12]protoimpl.MessageType
 var xxx_Test_protoFile_goTypes = []interface{}{
 	(Enum)(0),             // 0: pb3.Enum
 	(Enums_NestedEnum)(0), // 1: pb3.Enums.NestedEnum
@@ -718,11 +765,12 @@
 	(*Nested)(nil),        // 5: pb3.Nested
 	(*Oneofs)(nil),        // 6: pb3.Oneofs
 	(*Maps)(nil),          // 7: pb3.Maps
-	nil,                   // 8: pb3.Maps.Int32ToStrEntry
-	nil,                   // 9: pb3.Maps.BoolToUint32Entry
-	nil,                   // 10: pb3.Maps.Uint64ToEnumEntry
-	nil,                   // 11: pb3.Maps.StrToNestedEntry
-	nil,                   // 12: pb3.Maps.StrToOneofsEntry
+	(*JSONNames)(nil),     // 8: pb3.JSONNames
+	nil,                   // 9: pb3.Maps.Int32ToStrEntry
+	nil,                   // 10: pb3.Maps.BoolToUint32Entry
+	nil,                   // 11: pb3.Maps.Uint64ToEnumEntry
+	nil,                   // 12: pb3.Maps.StrToNestedEntry
+	nil,                   // 13: pb3.Maps.StrToOneofsEntry
 }
 var xxx_Test_protoFile_depIdxs = []int32{
 	0,  // pb3.Enums.s_enum:type_name -> pb3.Enum
@@ -731,18 +779,18 @@
 	5,  // pb3.Nested.s_nested:type_name -> pb3.Nested
 	0,  // pb3.Oneofs.oneof_enum:type_name -> pb3.Enum
 	5,  // pb3.Oneofs.oneof_nested:type_name -> pb3.Nested
-	8,  // pb3.Maps.int32_to_str:type_name -> pb3.Maps.Int32ToStrEntry
-	9,  // pb3.Maps.bool_to_uint32:type_name -> pb3.Maps.BoolToUint32Entry
-	10, // pb3.Maps.uint64_to_enum:type_name -> pb3.Maps.Uint64ToEnumEntry
-	11, // pb3.Maps.str_to_nested:type_name -> pb3.Maps.StrToNestedEntry
-	12, // pb3.Maps.str_to_oneofs:type_name -> pb3.Maps.StrToOneofsEntry
+	9,  // pb3.Maps.int32_to_str:type_name -> pb3.Maps.Int32ToStrEntry
+	10, // pb3.Maps.bool_to_uint32:type_name -> pb3.Maps.BoolToUint32Entry
+	11, // pb3.Maps.uint64_to_enum:type_name -> pb3.Maps.Uint64ToEnumEntry
+	12, // pb3.Maps.str_to_nested:type_name -> pb3.Maps.StrToNestedEntry
+	13, // pb3.Maps.str_to_oneofs:type_name -> pb3.Maps.StrToOneofsEntry
 	0,  // pb3.Maps.Uint64ToEnumEntry.value:type_name -> pb3.Enum
 	5,  // pb3.Maps.StrToNestedEntry.value:type_name -> pb3.Nested
 	6,  // pb3.Maps.StrToOneofsEntry.value:type_name -> pb3.Oneofs
 }
 
 func init() {
-	var messageTypes [11]protoreflect.MessageType
+	var messageTypes [12]protoreflect.MessageType
 	Test_protoFile = protoimpl.FileBuilder{
 		RawDescriptor:      fileDescriptor_33e0a17922cde063,
 		GoTypes:            xxx_Test_protoFile_goTypes,
@@ -750,7 +798,7 @@
 		EnumOutputTypes:    xxx_Test_protoFile_enumTypes[:],
 		MessageOutputTypes: messageTypes[:],
 	}.Init()
-	messageGoTypes := xxx_Test_protoFile_goTypes[2:][:11]
+	messageGoTypes := xxx_Test_protoFile_goTypes[2:][:12]
 	for i, mt := range messageTypes[:] {
 		xxx_Test_protoFile_messageTypes[i].GoType = reflect.TypeOf(messageGoTypes[i])
 		xxx_Test_protoFile_messageTypes[i].PBType = mt
diff --git a/encoding/testprotos/pb3/test.proto b/encoding/testprotos/pb3/test.proto
index 20638d6..598c268 100644
--- a/encoding/testprotos/pb3/test.proto
+++ b/encoding/testprotos/pb3/test.proto
@@ -81,3 +81,8 @@
   map<string, Nested> str_to_nested = 4;
   map<string, Oneofs> str_to_oneofs = 5;
 }
+
+// Message for testing json_name option.
+message JSONNames {
+  string s_string = 1 [json_name = "foo_bar"];
+}