From c9fd53a3b742f2a34c527cbe0833c5bc081e6ec3 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 20 Jul 2015 11:48:24 +0100 Subject: First part of JSON formatting for well-known types. I think we need a reflection API rethink before doing the rest. --- .../src/Google.Protobuf.Test/JsonFormatterTest.cs | 27 ++++++++++++++++++++ csharp/src/Google.Protobuf/JsonFormatter.cs | 29 +++++++++++++++++++++- .../Reflection/MessageDescriptor.cs | 25 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs index 5441bf47..a6715698 100644 --- a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs +++ b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs @@ -257,5 +257,32 @@ namespace Google.Protobuf formatter = new JsonFormatter(new JsonFormatter.Settings(true)); 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")); + } } } diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs index dacc7221..a06e6545 100644 --- a/csharp/src/Google.Protobuf/JsonFormatter.cs +++ b/csharp/src/Google.Protobuf/JsonFormatter.cs @@ -35,6 +35,7 @@ using System.Collections; using System.Globalization; using System.Text; using Google.Protobuf.Reflection; +using Google.Protobuf.WellKnownTypes; namespace Google.Protobuf { @@ -121,6 +122,9 @@ namespace Google.Protobuf { ThrowHelper.ThrowIfNull(message, "message"); 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); return builder.ToString(); } @@ -375,13 +379,36 @@ namespace Google.Protobuf break; case FieldType.Message: 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; default: throw new ArgumentException("Invalid field type: " + descriptor.FieldType); } } + /// + /// Central interception point for well-known type formatting. Any well-known types which + /// don't need special handling can fall back to WriteMessage. + /// + 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) { builder.Append("[ "); diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs index b6351d36..1c22c460 100644 --- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs @@ -40,6 +40,20 @@ namespace Google.Protobuf.Reflection /// public sealed class MessageDescriptor : DescriptorBase { + private static readonly HashSet WellKnownTypeNames = new HashSet + { + "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 MessageDescriptor containingType; private readonly IList nestedTypes; @@ -79,6 +93,17 @@ namespace Google.Protobuf.Reflection internal DescriptorProto Proto { get { return proto; } } + /// + /// Returns whether this message is one of the "well known types" which may have runtime/protoc support. + /// + internal bool IsWellKnownType + { + get + { + return File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name); + } + } + /// /// If this is a nested type, get the outer descriptor, otherwise null. /// -- cgit v1.2.3