encoding/protojson: use synthetic @type field for Any messages

In order for the synthetic @type field to potentially get reordered,
we implement insertion of that synthetic field by adding it
as a synthetic field that Range may iterate over.

This change sets up this code to more readily support a
hypothetical serialization mode for canonical serialization.

Change-Id: Ia0015a1a0804c15805dc5f3a3511fcf0f8513418
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/243817
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/encoding/protojson/decode.go b/encoding/protojson/decode.go
index e68a268..e6953ad 100644
--- a/encoding/protojson/decode.go
+++ b/encoding/protojson/decode.go
@@ -124,15 +124,6 @@
 		return d.unexpectedTokenError(tok)
 	}
 
-	if err := d.unmarshalFields(m, skipTypeURL); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// unmarshalFields unmarshals the fields into the given protoreflect.Message.
-func (d decoder) unmarshalFields(m pref.Message, skipTypeURL bool) error {
 	messageDesc := m.Descriptor()
 	if !flags.ProtoLegacy && messageset.IsMessageSet(messageDesc) {
 		return errors.New("no support for proto1 MessageSets")
diff --git a/encoding/protojson/encode.go b/encoding/protojson/encode.go
index 7dde32e..ba971f0 100644
--- a/encoding/protojson/encode.go
+++ b/encoding/protojson/encode.go
@@ -11,11 +11,13 @@
 	"google.golang.org/protobuf/internal/encoding/json"
 	"google.golang.org/protobuf/internal/encoding/messageset"
 	"google.golang.org/protobuf/internal/errors"
+	"google.golang.org/protobuf/internal/filedesc"
 	"google.golang.org/protobuf/internal/flags"
 	"google.golang.org/protobuf/internal/genid"
 	"google.golang.org/protobuf/internal/order"
 	"google.golang.org/protobuf/internal/pragma"
 	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protoreflect"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/reflect/protoregistry"
 )
@@ -131,7 +133,7 @@
 	}
 
 	enc := encoder{internalEnc, o}
-	if err := enc.marshalMessage(m.ProtoReflect()); err != nil {
+	if err := enc.marshalMessage(m.ProtoReflect(), ""); err != nil {
 		return nil, err
 	}
 	if o.AllowPartial {
@@ -145,19 +147,28 @@
 	opts MarshalOptions
 }
 
-// marshalMessage marshals the given protoreflect.Message.
-func (e encoder) marshalMessage(m pref.Message) error {
-	if marshal := wellKnownTypeMarshaler(m.Descriptor().FullName()); marshal != nil {
-		return marshal(e, m)
-	}
+// typeFieldDesc is a synthetic field descriptor used for the "@type" field.
+var typeFieldDesc = func() protoreflect.FieldDescriptor {
+	var fd filedesc.Field
+	fd.L0.FullName = "@type"
+	fd.L0.Index = -1
+	fd.L1.Cardinality = protoreflect.Optional
+	fd.L1.Kind = protoreflect.StringKind
+	return &fd
+}()
 
-	e.StartObject()
-	defer e.EndObject()
-	if err := e.marshalFields(m); err != nil {
-		return err
-	}
+// typeURLFieldRanger wraps a protoreflect.Message and modifies its Range method
+// to additionally iterate over a synthetic field for the type URL.
+type typeURLFieldRanger struct {
+	order.FieldRanger
+	typeURL string
+}
 
-	return nil
+func (m typeURLFieldRanger) Range(f func(pref.FieldDescriptor, pref.Value) bool) {
+	if !f(typeFieldDesc, pref.ValueOfString(m.typeURL)) {
+		return
+	}
+	m.FieldRanger.Range(f)
 }
 
 // unpopulatedFieldRanger wraps a protoreflect.Message and modifies its Range
@@ -185,16 +196,28 @@
 	m.Message.Range(f)
 }
 
-// marshalFields marshals the fields in the given protoreflect.Message.
-func (e encoder) marshalFields(m pref.Message) error {
+// marshalMessage marshals the fields in the given protoreflect.Message.
+// If the typeURL is non-empty, then a synthetic "@type" field is injected
+// containing the URL as the value.
+func (e encoder) marshalMessage(m pref.Message, typeURL string) error {
 	if !flags.ProtoLegacy && messageset.IsMessageSet(m.Descriptor()) {
 		return errors.New("no support for proto1 MessageSets")
 	}
 
+	if marshal := wellKnownTypeMarshaler(m.Descriptor().FullName()); marshal != nil {
+		return marshal(e, m)
+	}
+
+	e.StartObject()
+	defer e.EndObject()
+
 	var fields order.FieldRanger = m
 	if e.opts.EmitUnpopulated {
 		fields = unpopulatedFieldRanger{m}
 	}
+	if typeURL != "" {
+		fields = typeURLFieldRanger{fields, typeURL}
+	}
 
 	var err error
 	order.RangeFields(fields, order.IndexNameFieldOrder, func(fd pref.FieldDescriptor, v pref.Value) bool {
@@ -278,7 +301,7 @@
 		}
 
 	case pref.MessageKind, pref.GroupKind:
-		if err := e.marshalMessage(val.Message()); err != nil {
+		if err := e.marshalMessage(val.Message(), ""); err != nil {
 			return err
 		}
 
diff --git a/encoding/protojson/encode_test.go b/encoding/protojson/encode_test.go
index 0cca937..5e1570a 100644
--- a/encoding/protojson/encode_test.go
+++ b/encoding/protojson/encode_test.go
@@ -1624,6 +1624,34 @@
   "optString": "embedded inside Any"
 }`,
 	}, {
+		desc: "Any with EmitUnpopulated",
+		mo: protojson.MarshalOptions{
+			EmitUnpopulated: true,
+		},
+		input: func() proto.Message {
+			return &anypb.Any{
+				TypeUrl: string(new(pb3.Scalars).ProtoReflect().Descriptor().FullName()),
+			}
+		}(),
+		want: `{
+  "@type": "pb3.Scalars",
+  "sBool": false,
+  "sInt32": 0,
+  "sInt64": "0",
+  "sUint32": 0,
+  "sUint64": "0",
+  "sSint32": 0,
+  "sSint64": "0",
+  "sFixed32": 0,
+  "sFixed64": "0",
+  "sSfixed32": 0,
+  "sSfixed64": "0",
+  "sFloat": 0,
+  "sDouble": 0,
+  "sBytes": "",
+  "sString": ""
+}`,
+	}, {
 		desc: "Any with invalid UTF8",
 		input: func() proto.Message {
 			m := &pb2.Nested{
diff --git a/encoding/protojson/well_known_types.go b/encoding/protojson/well_known_types.go
index def7377..73b038e 100644
--- a/encoding/protojson/well_known_types.go
+++ b/encoding/protojson/well_known_types.go
@@ -106,13 +106,11 @@
 	fdType := fds.ByNumber(genid.Any_TypeUrl_field_number)
 	fdValue := fds.ByNumber(genid.Any_Value_field_number)
 
-	// Start writing the JSON object.
-	e.StartObject()
-	defer e.EndObject()
-
 	if !m.Has(fdType) {
 		if !m.Has(fdValue) {
 			// If message is empty, marshal out empty JSON object.
+			e.StartObject()
+			e.EndObject()
 			return nil
 		} else {
 			// Return error if type_url field is not set, but value is set.
@@ -123,14 +121,8 @@
 	typeVal := m.Get(fdType)
 	valueVal := m.Get(fdValue)
 
-	// Marshal out @type field.
-	typeURL := typeVal.String()
-	e.WriteName("@type")
-	if err := e.WriteString(typeURL); err != nil {
-		return err
-	}
-
 	// Resolve the type in order to unmarshal value field.
+	typeURL := typeVal.String()
 	emt, err := e.opts.Resolver.FindMessageByURL(typeURL)
 	if err != nil {
 		return errors.New("%s: unable to resolve %q: %v", genid.Any_message_fullname, typeURL, err)
@@ -149,12 +141,21 @@
 	// with corresponding custom JSON encoding of the embedded message as a
 	// field.
 	if marshal := wellKnownTypeMarshaler(emt.Descriptor().FullName()); marshal != nil {
+		e.StartObject()
+		defer e.EndObject()
+
+		// Marshal out @type field.
+		e.WriteName("@type")
+		if err := e.WriteString(typeURL); err != nil {
+			return err
+		}
+
 		e.WriteName("value")
 		return marshal(e, em)
 	}
 
 	// Else, marshal out the embedded message's fields in this Any object.
-	if err := e.marshalFields(em); err != nil {
+	if err := e.marshalMessage(em, typeURL); err != nil {
 		return err
 	}