Эх сурвалжийг харах

First part of JSON formatting for well-known types. I think we need a reflection API rethink before doing the rest.

Jon Skeet 10 жил өмнө
parent
commit
c9fd53a3b7

+ 27 - 0
csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs

@@ -257,5 +257,32 @@ namespace Google.Protobuf
             formatter = new JsonFormatter(new JsonFormatter.Settings(true));
             formatter = new JsonFormatter(new JsonFormatter.Settings(true));
             Assert.AreEqual(expectedJson, formatter.Format(message));
             Assert.AreEqual(expectedJson, formatter.Format(message));
         }
         }
+
+        [Test]
+        public void WrapperFormatting_Single()
+        {
+            // Just a few examples, handling both classes and value types, and
+            // default vs non-default values
+            var message = new TestWellKnownTypes
+            {
+                Int64Field = 10,
+                Int32Field = 0,
+                BytesField = ByteString.FromBase64("ABCD"),
+                StringField = ""
+            };
+            var expectedJson = "{ \"int64Field\": \"10\", \"int32Field\": 0, \"stringField\": \"\", \"bytesField\": \"ABCD\" }";
+            Assert.AreEqual(expectedJson, JsonFormatter.Default.Format(message));
+        }
+
+        [Test]
+        public void WrapperFormatting_IncludeNull()
+        {
+            // The actual JSON here is very large because there are lots of fields. Just test a couple of them.
+            var message = new TestWellKnownTypes { Int32Field = 10 };
+            var formatter = new JsonFormatter(new JsonFormatter.Settings(true));
+            var actualJson = formatter.Format(message);
+            Assert.IsTrue(actualJson.Contains("\"int64Field\": null"));
+            Assert.IsFalse(actualJson.Contains("\"int32Field\": null"));
+        }
     }
     }
 }
 }

+ 28 - 1
csharp/src/Google.Protobuf/JsonFormatter.cs

@@ -35,6 +35,7 @@ using System.Collections;
 using System.Globalization;
 using System.Globalization;
 using System.Text;
 using System.Text;
 using Google.Protobuf.Reflection;
 using Google.Protobuf.Reflection;
+using Google.Protobuf.WellKnownTypes;
 
 
 namespace Google.Protobuf
 namespace Google.Protobuf
 {
 {
@@ -121,6 +122,9 @@ namespace Google.Protobuf
         {
         {
             ThrowHelper.ThrowIfNull(message, "message");
             ThrowHelper.ThrowIfNull(message, "message");
             StringBuilder builder = new StringBuilder();
             StringBuilder builder = new StringBuilder();
+            // TODO(jonskeet): Handle well-known types here.
+            // Our reflection support needs improving so that we can get at the descriptor
+            // to find out whether *this* message is a well-known type.
             WriteMessage(builder, message);
             WriteMessage(builder, message);
             return builder.ToString();
             return builder.ToString();
         }
         }
@@ -375,13 +379,36 @@ namespace Google.Protobuf
                     break;
                     break;
                 case FieldType.Message:
                 case FieldType.Message:
                 case FieldType.Group: // Never expect to get this, but...
                 case FieldType.Group: // Never expect to get this, but...
-                    WriteMessage(builder, (IReflectedMessage) value);
+                    if (descriptor.MessageType.IsWellKnownType)
+                    {
+                        WriteWellKnownTypeValue(builder, descriptor, value);
+                    }
+                    else
+                    {
+                        WriteMessage(builder, (IReflectedMessage) value);
+                    }
                     break;
                     break;
                 default:
                 default:
                     throw new ArgumentException("Invalid field type: " + descriptor.FieldType);
                     throw new ArgumentException("Invalid field type: " + descriptor.FieldType);
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Central interception point for well-known type formatting. Any well-known types which
+        /// don't need special handling can fall back to WriteMessage.
+        /// </summary>
+        private void WriteWellKnownTypeValue(StringBuilder builder, FieldDescriptor descriptor, object value)
+        {
+            // For wrapper types, the value will be the (possibly boxed) "native" value,
+            // so we can write it as if we were unconditionally writing the Value field for the wrapper type.
+            if (descriptor.MessageType.File == Int32Value.Descriptor.File && value != null)
+            {
+                WriteSingleValue(builder, descriptor.MessageType.FindFieldByNumber(1), value);
+                return;
+            }
+            WriteMessage(builder, (IReflectedMessage) value);
+        }
+
         private void WriteList(StringBuilder builder, IFieldAccessor accessor, IList list)
         private void WriteList(StringBuilder builder, IFieldAccessor accessor, IList list)
         {
         {
             builder.Append("[ ");
             builder.Append("[ ");

+ 25 - 0
csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs

@@ -40,6 +40,20 @@ namespace Google.Protobuf.Reflection
     /// </summary>
     /// </summary>
     public sealed class MessageDescriptor : DescriptorBase
     public sealed class MessageDescriptor : DescriptorBase
     {
     {
+        private static readonly HashSet<string> WellKnownTypeNames = new HashSet<string>
+        {
+            "google/protobuf/any.proto",
+            "google/protobuf/api.proto",
+            "google/protobuf/duration.proto",
+            "google/protobuf/empty.proto",
+            "google/protobuf/wrappers.proto",
+            "google/protobuf/timestamp.proto",
+            "google/protobuf/field_mask.proto",
+            "google/protobuf/source_context.proto",
+            "google/protobuf/struct.proto",
+            "google/protobuf/type.proto",
+        };
+
         private readonly DescriptorProto proto;
         private readonly DescriptorProto proto;
         private readonly MessageDescriptor containingType;
         private readonly MessageDescriptor containingType;
         private readonly IList<MessageDescriptor> nestedTypes;
         private readonly IList<MessageDescriptor> nestedTypes;
@@ -79,6 +93,17 @@ namespace Google.Protobuf.Reflection
 
 
         internal DescriptorProto Proto { get { return proto; } }
         internal DescriptorProto Proto { get { return proto; } }
 
 
+        /// <summary>
+        /// Returns whether this message is one of the "well known types" which may have runtime/protoc support.
+        /// </summary>
+        internal bool IsWellKnownType
+        {
+            get
+            {
+                return File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name);
+            }
+        }
+
         /// <value>
         /// <value>
         /// If this is a nested type, get the outer descriptor, otherwise null.
         /// If this is a nested type, get the outer descriptor, otherwise null.
         /// </value>
         /// </value>