diff options
Diffstat (limited to 'csharp/src/Google.Protobuf/JsonFormatter.cs')
-rw-r--r-- | csharp/src/Google.Protobuf/JsonFormatter.cs | 106 |
1 files changed, 65 insertions, 41 deletions
diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs index 45941b39..bde17903 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. @@ -140,7 +142,7 @@ namespace Google.Protobuf StringBuilder builder = new StringBuilder(); if (message.Descriptor.IsWellKnownType) { - WriteWellKnownTypeValue(builder, message.Descriptor, message, false); + WriteWellKnownTypeValue(builder, message.Descriptor, message); } else { @@ -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) @@ -368,7 +393,7 @@ namespace Google.Protobuf IMessage message = (IMessage) value; if (message.Descriptor.IsWellKnownType) { - WriteWellKnownTypeValue(builder, message.Descriptor, value, true); + WriteWellKnownTypeValue(builder, message.Descriptor, value); } else { @@ -387,7 +412,7 @@ namespace Google.Protobuf /// values are using the embedded well-known types, in order to allow for dynamic messages /// in the future. /// </summary> - private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescriptor descriptor, object value, bool inField) + private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescriptor descriptor, object value) { // Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*, // this would do the right thing. @@ -413,17 +438,17 @@ namespace Google.Protobuf } if (descriptor.FullName == Timestamp.Descriptor.FullName) { - MaybeWrapInString(builder, value, WriteTimestamp, inField); + WriteTimestamp(builder, (IMessage) value); return; } if (descriptor.FullName == Duration.Descriptor.FullName) { - MaybeWrapInString(builder, value, WriteDuration, inField); + WriteDuration(builder, (IMessage) value); return; } if (descriptor.FullName == FieldMask.Descriptor.FullName) { - MaybeWrapInString(builder, value, WriteFieldMask, inField); + WriteFieldMask(builder, (IMessage) value); return; } if (descriptor.FullName == Struct.Descriptor.FullName) @@ -450,26 +475,9 @@ namespace Google.Protobuf WriteMessage(builder, (IMessage) value); } - /// <summary> - /// Some well-known types end up as string values... so they need wrapping in quotes, but only - /// when they're being used as fields within another message. - /// </summary> - private void MaybeWrapInString(StringBuilder builder, object value, Action<StringBuilder, IMessage> action, bool inField) - { - if (inField) - { - builder.Append('"'); - action(builder, (IMessage) value); - builder.Append('"'); - } - else - { - action(builder, (IMessage) value); - } - } - private void WriteTimestamp(StringBuilder builder, IMessage value) { + builder.Append('"'); // TODO: In the common case where this *is* using the built-in Timestamp type, we could // avoid all the reflection at this point, by casting to Timestamp. In the interests of // avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove @@ -484,11 +492,12 @@ namespace Google.Protobuf DateTime dateTime = normalized.ToDateTime(); builder.Append(dateTime.ToString("yyyy'-'MM'-'dd'T'HH:mm:ss", CultureInfo.InvariantCulture)); AppendNanoseconds(builder, Math.Abs(normalized.Nanos)); - builder.Append('Z'); + builder.Append("Z\""); } private void WriteDuration(StringBuilder builder, IMessage value) { + builder.Append('"'); // TODO: Same as for WriteTimestamp int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value); long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value); @@ -505,17 +514,23 @@ namespace Google.Protobuf builder.Append(normalized.Seconds.ToString("d", CultureInfo.InvariantCulture)); AppendNanoseconds(builder, Math.Abs(normalized.Nanos)); - builder.Append('s'); + builder.Append("s\""); } private void WriteFieldMask(StringBuilder builder, IMessage value) { IList paths = (IList) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value); - AppendEscapedString(builder, string.Join(",", paths.Cast<string>().Select(ToCamelCase))); + WriteString(builder, string.Join(",", paths.Cast<string>().Select(ToCamelCase))); } 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); @@ -535,7 +550,7 @@ namespace Google.Protobuf builder.Append(PropertySeparator); WriteString(builder, AnyWellKnownTypeValueField); builder.Append(NameValueSeparator); - WriteWellKnownTypeValue(builder, descriptor, message, true); + WriteWellKnownTypeValue(builder, descriptor, message); } else { @@ -544,6 +559,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('/'); @@ -626,7 +658,7 @@ namespace Google.Protobuf case Value.ListValueFieldNumber: // Structs and ListValues are nested messages, and already well-known types. var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message); - WriteWellKnownTypeValue(builder, nestedMessage.Descriptor, nestedMessage, true); + WriteWellKnownTypeValue(builder, nestedMessage.Descriptor, nestedMessage); return; case Value.NullValueFieldNumber: WriteNull(builder); @@ -723,15 +755,6 @@ namespace Google.Protobuf private void WriteString(StringBuilder builder, string text) { builder.Append('"'); - AppendEscapedString(builder, text); - builder.Append('"'); - } - - /// <summary> - /// Appends the given text to the string builder, escaping as required. - /// </summary> - private void AppendEscapedString(StringBuilder builder, string text) - { for (int i = 0; i < text.Length; i++) { char c = text[i]; @@ -791,6 +814,7 @@ namespace Google.Protobuf break; } } + builder.Append('"'); } private const string Hex = "0123456789abcdef"; |