فهرست منبع

Make ToString() valid without a type registry

This addresses issue #1008, by creating a JsonFormatter which is private and only different
to JsonFormatter.Default in terms of reference equality.

Other plausible designs:

- The same, but expose the diagnostic-only formatter
- Add something to settings to say "I don't have a type registry at all"
- Change the behaviour of JsonFormatter.Default (bad idea IMO, as we really *don't* want the result of this used as regular JSON to be parsed)

Note that just trying to find a separate fix to issue #933 and using that to override Any.ToString() differently wouldn't work for messages that *contain* an Any.

Generated code changes follow in the next commit.
Jon Skeet 9 سال پیش
والد
کامیت
aabc6c411a

+ 23 - 0
csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs

@@ -62,5 +62,28 @@ namespace Google.Protobuf.WellKnownTypes
             var unpacked = any.Unpack<TestAllTypes>();
             Assert.AreEqual(message, unpacked);
         }
+
+        [Test]
+        public void ToString_WithValues()
+        {
+            var message = SampleMessages.CreateFullTestAllTypes();
+            var any = Any.Pack(message);
+            var text = any.ToString();
+            Assert.That(text, Is.StringContaining("\"@value\": \"" + message.ToByteString().ToBase64() + "\""));
+        }
+
+        [Test]
+        public void ToString_Empty()
+        {
+            var any = new Any();
+            Assert.AreEqual("{ \"@type\": \"\", \"@value\": \"\" }", any.ToString());
+        }
+
+        [Test]
+        public void ToString_MessageContainingAny()
+        {
+            var message = new TestWellKnownTypes { AnyField = new Any() };
+            Assert.AreEqual("{ \"anyField\": { \"@type\": \"\", \"@value\": \"\" } }", message.ToString());
+        }
     }
 }

+ 51 - 3
csharp/src/Google.Protobuf/JsonFormatter.cs

@@ -56,17 +56,19 @@ namespace Google.Protobuf
     public sealed class JsonFormatter
     {
         internal const string AnyTypeUrlField = "@type";
+        internal const string AnyDiagnosticValueField = "@value";
         internal const string AnyWellKnownTypeValueField = "value";
         private const string TypeUrlPrefix = "type.googleapis.com";
         private const string NameValueSeparator = ": ";
         private const string PropertySeparator = ", ";
 
-        private static JsonFormatter defaultInstance = new JsonFormatter(Settings.Default);
-
         /// <summary>
         /// Returns a formatter using the default settings.
         /// </summary>
-        public static JsonFormatter Default { get { return defaultInstance; } }
+        public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default);
+
+        // A JSON formatter which *only* exists 
+        private static readonly JsonFormatter diagnosticFormatter = new JsonFormatter(Settings.Default);
 
         /// <summary>
         /// The JSON representation of the first 160 characters of Unicode.
@@ -149,6 +151,29 @@ namespace Google.Protobuf
             return builder.ToString();
         }
 
+        /// <summary>
+        /// Converts a message to JSON for diagnostic purposes with no extra context.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// This differs from calling <see cref="Format(IMessage)"/> on the default JSON
+        /// formatter in its handling of <see cref="Any"/>. As no type registry is available
+        /// in <see cref="object.ToString"/> calls, the normal way of resolving the type of
+        /// an <c>Any</c> message cannot be applied. Instead, a JSON property named <c>@value</c>
+        /// is included with the base64 data from the <see cref="Any.Value"/> property of the message.
+        /// </para>
+        /// <para>The value returned by this method is only designed to be used for diagnostic
+        /// purposes. It may not be parsable by <see cref="JsonParser"/>, and may not be parsable
+        /// by other Protocol Buffer implementations.</para>
+        /// </remarks>
+        /// <param name="message">The message to format for diagnostic purposes.</param>
+        /// <returns>The diagnostic-only JSON representation of the message</returns>
+        public static string ToDiagnosticString(IMessage message)
+        {
+            Preconditions.CheckNotNull(message, nameof(message));
+            return diagnosticFormatter.Format(message);
+        }
+
         private void WriteMessage(StringBuilder builder, IMessage message)
         {
             if (message == null)
@@ -516,6 +541,12 @@ namespace Google.Protobuf
 
         private void WriteAny(StringBuilder builder, IMessage value)
         {
+            if (ReferenceEquals(this, diagnosticFormatter))
+            {
+                WriteDiagnosticOnlyAny(builder, value);
+                return;
+            }
+
             string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
             ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
             string typeName = GetTypeName(typeUrl);
@@ -544,6 +575,23 @@ namespace Google.Protobuf
             builder.Append(" }");
         }
 
+        private void WriteDiagnosticOnlyAny(StringBuilder builder, IMessage value)
+        {
+            string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
+            ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
+            builder.Append("{ ");
+            WriteString(builder, AnyTypeUrlField);
+            builder.Append(NameValueSeparator);
+            WriteString(builder, typeUrl);
+            builder.Append(PropertySeparator);
+            WriteString(builder, AnyDiagnosticValueField);
+            builder.Append(NameValueSeparator);
+            builder.Append('"');
+            builder.Append(data.ToBase64());
+            builder.Append('"');
+            builder.Append(" }");
+        }
+
         internal static string GetTypeName(String typeUrl)
         {
             string[] parts = typeUrl.Split('/');

+ 1 - 1
src/google/protobuf/compiler/csharp/csharp_message.cc

@@ -353,7 +353,7 @@ void MessageGenerator::GenerateFrameworkMethods(io::Printer* printer) {
 
     printer->Print(
         "public override string ToString() {\n"
-        "  return pb::JsonFormatter.Default.Format(this);\n"
+        "  return pb::JsonFormatter.ToDiagnosticString(this);\n"
         "}\n\n");
 }