encoding/jsonpb: add support for marshaling well-known types

Also, changed MarshalOptions.Compact to Indent for consistency with v1
and to make compact as the default.

Change-Id: Id08aaa5ca5656f18e7925d2eabc0b6b055b1cebb
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/168352
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/encoding/jsonpb/encode.go b/encoding/jsonpb/encode.go
index 3830f31..a9ca581 100644
--- a/encoding/jsonpb/encode.go
+++ b/encoding/jsonpb/encode.go
@@ -6,6 +6,7 @@
 
 import (
 	"encoding/base64"
+	"fmt"
 	"sort"
 
 	"github.com/golang/protobuf/v2/internal/encoding/json"
@@ -13,6 +14,7 @@
 	"github.com/golang/protobuf/v2/internal/pragma"
 	"github.com/golang/protobuf/v2/proto"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
+	"github.com/golang/protobuf/v2/reflect/protoregistry"
 
 	descpb "github.com/golang/protobuf/v2/types/descriptor"
 )
@@ -26,18 +28,21 @@
 type MarshalOptions struct {
 	pragma.NoUnkeyedLiterals
 
-	// Set Compact to true to have output in a single line with no line breaks.
-	Compact bool
+	// If Indent is a non-empty string, it causes entries for an Array or Object
+	// to be preceded by the indent and trailed by a newline. Indent can only be
+	// composed of space or tab characters.
+	Indent string
+
+	// Resolver is the registry used for type lookups when marshaling
+	// google.protobuf.Any messages. If Resolver is not set, marshaling will
+	// default to using protoregistry.GlobalTypes.
+	Resolver *protoregistry.Types
 }
 
-// Marshal returns the given proto.Message in JSON format using options in MarshalOptions object.
+// Marshal marshals the given proto.Message in the JSON format using options in
+// MarshalOptions.
 func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
-	indent := "  "
-	if o.Compact {
-		indent = ""
-	}
-
-	enc, err := newEncoder(indent)
+	enc, err := newEncoder(o.Indent, o.Resolver)
 	if err != nil {
 		return nil, err
 	}
@@ -53,21 +58,42 @@
 // encoder encodes protoreflect values into JSON.
 type encoder struct {
 	*json.Encoder
+	resolver *protoregistry.Types
 }
 
-func newEncoder(indent string) (encoder, error) {
+func newEncoder(indent string, resolver *protoregistry.Types) (encoder, error) {
 	enc, err := json.NewEncoder(indent)
 	if err != nil {
-		return encoder{}, errors.New("error in constructing an encoder: %v", err)
+		return encoder{}, err
 	}
-	return encoder{enc}, nil
+	if resolver == nil {
+		resolver = protoregistry.GlobalTypes
+	}
+	return encoder{
+		Encoder:  enc,
+		resolver: resolver,
+	}, nil
 }
 
 // marshalMessage marshals the given protoreflect.Message.
 func (e encoder) marshalMessage(m pref.Message) error {
+	var nerr errors.NonFatal
+
+	if isCustomType(m.Type().FullName()) {
+		return e.marshalCustomType(m)
+	}
+
 	e.StartObject()
 	defer e.EndObject()
+	if err := e.marshalFields(m); !nerr.Merge(err) {
+		return err
+	}
 
+	return nerr.E
+}
+
+// marshalFields marshals the fields in the given protoreflect.Message.
+func (e encoder) marshalFields(m pref.Message) error {
 	var nerr errors.NonFatal
 	fieldDescs := m.Type().Fields()
 	knownFields := m.KnownFields()
@@ -85,12 +111,17 @@
 			continue
 		}
 
+		// An empty google.protobuf.Value should NOT be marshaled out.
+		// Hence need to check ahead for this.
+		val := knownFields.Get(num)
+		if isEmptyKnownValue(val, fd.MessageType()) {
+			continue
+		}
+
 		name := fd.JSONName()
 		if err := e.WriteName(name); !nerr.Merge(err) {
 			return err
 		}
-
-		val := knownFields.Get(num)
 		if err := e.marshalValue(val, fd); !nerr.Merge(err) {
 			return err
 		}
@@ -165,8 +196,12 @@
 		}
 
 	case pref.EnumKind:
+		enumType := fd.EnumType()
 		num := val.Enum()
-		if desc := fd.EnumType().Values().ByNumber(num); desc != nil {
+
+		if enumType.FullName() == "google.protobuf.NullValue" {
+			e.WriteNull()
+		} else if desc := enumType.Values().ByNumber(num); desc != nil {
 			err := e.WriteString(string(desc.Name()))
 			if !nerr.Merge(err) {
 				return err
@@ -182,7 +217,7 @@
 		}
 
 	default:
-		return errors.New("%v has unknown kind: %v", fd.FullName(), kind)
+		panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))
 	}
 	return nerr.E
 }