diff options
author | Jon Skeet <jonskeet@google.com> | 2015-12-15 09:23:38 +0000 |
---|---|---|
committer | Jon Skeet <jonskeet@google.com> | 2015-12-15 09:23:38 +0000 |
commit | aabc6c411a4ae2c5b63124d4079ea9b0dc0879c7 (patch) | |
tree | 2b82f7c54db9fe91944750650c7704e8fa66ed47 /csharp | |
parent | dc633398af5c87e1fe1ffcedaebd697c4775c918 (diff) | |
download | protobuf-aabc6c411a4ae2c5b63124d4079ea9b0dc0879c7.tar.gz protobuf-aabc6c411a4ae2c5b63124d4079ea9b0dc0879c7.tar.bz2 protobuf-aabc6c411a4ae2c5b63124d4079ea9b0dc0879c7.zip |
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.
Diffstat (limited to 'csharp')
-rw-r--r-- | csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs | 23 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/JsonFormatter.cs | 54 |
2 files changed, 74 insertions, 3 deletions
diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs index 0a2b8b32..f3593e5f 100644 --- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs +++ b/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()); + } } } diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs index 45941b39..7b99f314 100644 --- a/csharp/src/Google.Protobuf/JsonFormatter.cs +++ b/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('/'); |