From aabc6c411a4ae2c5b63124d4079ea9b0dc0879c7 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 15 Dec 2015 09:23:38 +0000 Subject: 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. --- csharp/src/Google.Protobuf/JsonFormatter.cs | 54 +++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) (limited to 'csharp/src/Google.Protobuf/JsonFormatter.cs') 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); - /// /// Returns a formatter using the default settings. /// - 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); /// /// The JSON representation of the first 160 characters of Unicode. @@ -149,6 +151,29 @@ namespace Google.Protobuf return builder.ToString(); } + /// + /// Converts a message to JSON for diagnostic purposes with no extra context. + /// + /// + /// + /// This differs from calling on the default JSON + /// formatter in its handling of . As no type registry is available + /// in calls, the normal way of resolving the type of + /// an Any message cannot be applied. Instead, a JSON property named @value + /// is included with the base64 data from the property of the message. + /// + /// The value returned by this method is only designed to be used for diagnostic + /// purposes. It may not be parsable by , and may not be parsable + /// by other Protocol Buffer implementations. + /// + /// The message to format for diagnostic purposes. + /// The diagnostic-only JSON representation of the message + 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('/'); -- cgit v1.2.3