1ffe3c632Sopenharmony_ci#region Copyright notice and license 2ffe3c632Sopenharmony_ci// Protocol Buffers - Google's data interchange format 3ffe3c632Sopenharmony_ci// Copyright 2015 Google Inc. All rights reserved. 4ffe3c632Sopenharmony_ci// https://developers.google.com/protocol-buffers/ 5ffe3c632Sopenharmony_ci// 6ffe3c632Sopenharmony_ci// Redistribution and use in source and binary forms, with or without 7ffe3c632Sopenharmony_ci// modification, are permitted provided that the following conditions are 8ffe3c632Sopenharmony_ci// met: 9ffe3c632Sopenharmony_ci// 10ffe3c632Sopenharmony_ci// * Redistributions of source code must retain the above copyright 11ffe3c632Sopenharmony_ci// notice, this list of conditions and the following disclaimer. 12ffe3c632Sopenharmony_ci// * Redistributions in binary form must reproduce the above 13ffe3c632Sopenharmony_ci// copyright notice, this list of conditions and the following disclaimer 14ffe3c632Sopenharmony_ci// in the documentation and/or other materials provided with the 15ffe3c632Sopenharmony_ci// distribution. 16ffe3c632Sopenharmony_ci// * Neither the name of Google Inc. nor the names of its 17ffe3c632Sopenharmony_ci// contributors may be used to endorse or promote products derived from 18ffe3c632Sopenharmony_ci// this software without specific prior written permission. 19ffe3c632Sopenharmony_ci// 20ffe3c632Sopenharmony_ci// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21ffe3c632Sopenharmony_ci// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22ffe3c632Sopenharmony_ci// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23ffe3c632Sopenharmony_ci// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24ffe3c632Sopenharmony_ci// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25ffe3c632Sopenharmony_ci// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26ffe3c632Sopenharmony_ci// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27ffe3c632Sopenharmony_ci// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28ffe3c632Sopenharmony_ci// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29ffe3c632Sopenharmony_ci// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30ffe3c632Sopenharmony_ci// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31ffe3c632Sopenharmony_ci#endregion 32ffe3c632Sopenharmony_ci 33ffe3c632Sopenharmony_ciusing System; 34ffe3c632Sopenharmony_ciusing System.Collections; 35ffe3c632Sopenharmony_ciusing System.Globalization; 36ffe3c632Sopenharmony_ciusing System.Text; 37ffe3c632Sopenharmony_ciusing Google.Protobuf.Reflection; 38ffe3c632Sopenharmony_ciusing Google.Protobuf.WellKnownTypes; 39ffe3c632Sopenharmony_ciusing System.IO; 40ffe3c632Sopenharmony_ciusing System.Linq; 41ffe3c632Sopenharmony_ciusing System.Collections.Generic; 42ffe3c632Sopenharmony_ciusing System.Reflection; 43ffe3c632Sopenharmony_ci 44ffe3c632Sopenharmony_cinamespace Google.Protobuf 45ffe3c632Sopenharmony_ci{ 46ffe3c632Sopenharmony_ci /// <summary> 47ffe3c632Sopenharmony_ci /// Reflection-based converter from messages to JSON. 48ffe3c632Sopenharmony_ci /// </summary> 49ffe3c632Sopenharmony_ci /// <remarks> 50ffe3c632Sopenharmony_ci /// <para> 51ffe3c632Sopenharmony_ci /// Instances of this class are thread-safe, with no mutable state. 52ffe3c632Sopenharmony_ci /// </para> 53ffe3c632Sopenharmony_ci /// <para> 54ffe3c632Sopenharmony_ci /// This is a simple start to get JSON formatting working. As it's reflection-based, 55ffe3c632Sopenharmony_ci /// it's not as quick as baking calls into generated messages - but is a simpler implementation. 56ffe3c632Sopenharmony_ci /// (This code is generally not heavily optimized.) 57ffe3c632Sopenharmony_ci /// </para> 58ffe3c632Sopenharmony_ci /// </remarks> 59ffe3c632Sopenharmony_ci public sealed class JsonFormatter 60ffe3c632Sopenharmony_ci { 61ffe3c632Sopenharmony_ci internal const string AnyTypeUrlField = "@type"; 62ffe3c632Sopenharmony_ci internal const string AnyDiagnosticValueField = "@value"; 63ffe3c632Sopenharmony_ci internal const string AnyWellKnownTypeValueField = "value"; 64ffe3c632Sopenharmony_ci private const string TypeUrlPrefix = "type.googleapis.com"; 65ffe3c632Sopenharmony_ci private const string NameValueSeparator = ": "; 66ffe3c632Sopenharmony_ci private const string PropertySeparator = ", "; 67ffe3c632Sopenharmony_ci 68ffe3c632Sopenharmony_ci /// <summary> 69ffe3c632Sopenharmony_ci /// Returns a formatter using the default settings. 70ffe3c632Sopenharmony_ci /// </summary> 71ffe3c632Sopenharmony_ci public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default); 72ffe3c632Sopenharmony_ci 73ffe3c632Sopenharmony_ci // A JSON formatter which *only* exists 74ffe3c632Sopenharmony_ci private static readonly JsonFormatter diagnosticFormatter = new JsonFormatter(Settings.Default); 75ffe3c632Sopenharmony_ci 76ffe3c632Sopenharmony_ci /// <summary> 77ffe3c632Sopenharmony_ci /// The JSON representation of the first 160 characters of Unicode. 78ffe3c632Sopenharmony_ci /// Empty strings are replaced by the static constructor. 79ffe3c632Sopenharmony_ci /// </summary> 80ffe3c632Sopenharmony_ci private static readonly string[] CommonRepresentations = { 81ffe3c632Sopenharmony_ci // C0 (ASCII and derivatives) control characters 82ffe3c632Sopenharmony_ci "\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00 83ffe3c632Sopenharmony_ci "\\u0004", "\\u0005", "\\u0006", "\\u0007", 84ffe3c632Sopenharmony_ci "\\b", "\\t", "\\n", "\\u000b", 85ffe3c632Sopenharmony_ci "\\f", "\\r", "\\u000e", "\\u000f", 86ffe3c632Sopenharmony_ci "\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10 87ffe3c632Sopenharmony_ci "\\u0014", "\\u0015", "\\u0016", "\\u0017", 88ffe3c632Sopenharmony_ci "\\u0018", "\\u0019", "\\u001a", "\\u001b", 89ffe3c632Sopenharmony_ci "\\u001c", "\\u001d", "\\u001e", "\\u001f", 90ffe3c632Sopenharmony_ci // Escaping of " and \ are required by www.json.org string definition. 91ffe3c632Sopenharmony_ci // Escaping of < and > are required for HTML security. 92ffe3c632Sopenharmony_ci "", "", "\\\"", "", "", "", "", "", // 0x20 93ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", 94ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", // 0x30 95ffe3c632Sopenharmony_ci "", "", "", "", "\\u003c", "", "\\u003e", "", 96ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", // 0x40 97ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", 98ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", // 0x50 99ffe3c632Sopenharmony_ci "", "", "", "", "\\\\", "", "", "", 100ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", // 0x60 101ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", 102ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "", // 0x70 103ffe3c632Sopenharmony_ci "", "", "", "", "", "", "", "\\u007f", 104ffe3c632Sopenharmony_ci // C1 (ISO 8859 and Unicode) extended control characters 105ffe3c632Sopenharmony_ci "\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80 106ffe3c632Sopenharmony_ci "\\u0084", "\\u0085", "\\u0086", "\\u0087", 107ffe3c632Sopenharmony_ci "\\u0088", "\\u0089", "\\u008a", "\\u008b", 108ffe3c632Sopenharmony_ci "\\u008c", "\\u008d", "\\u008e", "\\u008f", 109ffe3c632Sopenharmony_ci "\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90 110ffe3c632Sopenharmony_ci "\\u0094", "\\u0095", "\\u0096", "\\u0097", 111ffe3c632Sopenharmony_ci "\\u0098", "\\u0099", "\\u009a", "\\u009b", 112ffe3c632Sopenharmony_ci "\\u009c", "\\u009d", "\\u009e", "\\u009f" 113ffe3c632Sopenharmony_ci }; 114ffe3c632Sopenharmony_ci 115ffe3c632Sopenharmony_ci static JsonFormatter() 116ffe3c632Sopenharmony_ci { 117ffe3c632Sopenharmony_ci for (int i = 0; i < CommonRepresentations.Length; i++) 118ffe3c632Sopenharmony_ci { 119ffe3c632Sopenharmony_ci if (CommonRepresentations[i] == "") 120ffe3c632Sopenharmony_ci { 121ffe3c632Sopenharmony_ci CommonRepresentations[i] = ((char) i).ToString(); 122ffe3c632Sopenharmony_ci } 123ffe3c632Sopenharmony_ci } 124ffe3c632Sopenharmony_ci } 125ffe3c632Sopenharmony_ci 126ffe3c632Sopenharmony_ci private readonly Settings settings; 127ffe3c632Sopenharmony_ci 128ffe3c632Sopenharmony_ci private bool DiagnosticOnly => ReferenceEquals(this, diagnosticFormatter); 129ffe3c632Sopenharmony_ci 130ffe3c632Sopenharmony_ci /// <summary> 131ffe3c632Sopenharmony_ci /// Creates a new formatted with the given settings. 132ffe3c632Sopenharmony_ci /// </summary> 133ffe3c632Sopenharmony_ci /// <param name="settings">The settings.</param> 134ffe3c632Sopenharmony_ci public JsonFormatter(Settings settings) 135ffe3c632Sopenharmony_ci { 136ffe3c632Sopenharmony_ci this.settings = ProtoPreconditions.CheckNotNull(settings, nameof(settings)); 137ffe3c632Sopenharmony_ci } 138ffe3c632Sopenharmony_ci 139ffe3c632Sopenharmony_ci /// <summary> 140ffe3c632Sopenharmony_ci /// Formats the specified message as JSON. 141ffe3c632Sopenharmony_ci /// </summary> 142ffe3c632Sopenharmony_ci /// <param name="message">The message to format.</param> 143ffe3c632Sopenharmony_ci /// <returns>The formatted message.</returns> 144ffe3c632Sopenharmony_ci public string Format(IMessage message) 145ffe3c632Sopenharmony_ci { 146ffe3c632Sopenharmony_ci var writer = new StringWriter(); 147ffe3c632Sopenharmony_ci Format(message, writer); 148ffe3c632Sopenharmony_ci return writer.ToString(); 149ffe3c632Sopenharmony_ci } 150ffe3c632Sopenharmony_ci 151ffe3c632Sopenharmony_ci /// <summary> 152ffe3c632Sopenharmony_ci /// Formats the specified message as JSON. 153ffe3c632Sopenharmony_ci /// </summary> 154ffe3c632Sopenharmony_ci /// <param name="message">The message to format.</param> 155ffe3c632Sopenharmony_ci /// <param name="writer">The TextWriter to write the formatted message to.</param> 156ffe3c632Sopenharmony_ci /// <returns>The formatted message.</returns> 157ffe3c632Sopenharmony_ci public void Format(IMessage message, TextWriter writer) 158ffe3c632Sopenharmony_ci { 159ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(message, nameof(message)); 160ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(writer, nameof(writer)); 161ffe3c632Sopenharmony_ci 162ffe3c632Sopenharmony_ci if (message.Descriptor.IsWellKnownType) 163ffe3c632Sopenharmony_ci { 164ffe3c632Sopenharmony_ci WriteWellKnownTypeValue(writer, message.Descriptor, message); 165ffe3c632Sopenharmony_ci } 166ffe3c632Sopenharmony_ci else 167ffe3c632Sopenharmony_ci { 168ffe3c632Sopenharmony_ci WriteMessage(writer, message); 169ffe3c632Sopenharmony_ci } 170ffe3c632Sopenharmony_ci } 171ffe3c632Sopenharmony_ci 172ffe3c632Sopenharmony_ci /// <summary> 173ffe3c632Sopenharmony_ci /// Converts a message to JSON for diagnostic purposes with no extra context. 174ffe3c632Sopenharmony_ci /// </summary> 175ffe3c632Sopenharmony_ci /// <remarks> 176ffe3c632Sopenharmony_ci /// <para> 177ffe3c632Sopenharmony_ci /// This differs from calling <see cref="Format(IMessage)"/> on the default JSON 178ffe3c632Sopenharmony_ci /// formatter in its handling of <see cref="Any"/>. As no type registry is available 179ffe3c632Sopenharmony_ci /// in <see cref="object.ToString"/> calls, the normal way of resolving the type of 180ffe3c632Sopenharmony_ci /// an <c>Any</c> message cannot be applied. Instead, a JSON property named <c>@value</c> 181ffe3c632Sopenharmony_ci /// is included with the base64 data from the <see cref="Any.Value"/> property of the message. 182ffe3c632Sopenharmony_ci /// </para> 183ffe3c632Sopenharmony_ci /// <para>The value returned by this method is only designed to be used for diagnostic 184ffe3c632Sopenharmony_ci /// purposes. It may not be parsable by <see cref="JsonParser"/>, and may not be parsable 185ffe3c632Sopenharmony_ci /// by other Protocol Buffer implementations.</para> 186ffe3c632Sopenharmony_ci /// </remarks> 187ffe3c632Sopenharmony_ci /// <param name="message">The message to format for diagnostic purposes.</param> 188ffe3c632Sopenharmony_ci /// <returns>The diagnostic-only JSON representation of the message</returns> 189ffe3c632Sopenharmony_ci public static string ToDiagnosticString(IMessage message) 190ffe3c632Sopenharmony_ci { 191ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(message, nameof(message)); 192ffe3c632Sopenharmony_ci return diagnosticFormatter.Format(message); 193ffe3c632Sopenharmony_ci } 194ffe3c632Sopenharmony_ci 195ffe3c632Sopenharmony_ci private void WriteMessage(TextWriter writer, IMessage message) 196ffe3c632Sopenharmony_ci { 197ffe3c632Sopenharmony_ci if (message == null) 198ffe3c632Sopenharmony_ci { 199ffe3c632Sopenharmony_ci WriteNull(writer); 200ffe3c632Sopenharmony_ci return; 201ffe3c632Sopenharmony_ci } 202ffe3c632Sopenharmony_ci if (DiagnosticOnly) 203ffe3c632Sopenharmony_ci { 204ffe3c632Sopenharmony_ci ICustomDiagnosticMessage customDiagnosticMessage = message as ICustomDiagnosticMessage; 205ffe3c632Sopenharmony_ci if (customDiagnosticMessage != null) 206ffe3c632Sopenharmony_ci { 207ffe3c632Sopenharmony_ci writer.Write(customDiagnosticMessage.ToDiagnosticString()); 208ffe3c632Sopenharmony_ci return; 209ffe3c632Sopenharmony_ci } 210ffe3c632Sopenharmony_ci } 211ffe3c632Sopenharmony_ci writer.Write("{ "); 212ffe3c632Sopenharmony_ci bool writtenFields = WriteMessageFields(writer, message, false); 213ffe3c632Sopenharmony_ci writer.Write(writtenFields ? " }" : "}"); 214ffe3c632Sopenharmony_ci } 215ffe3c632Sopenharmony_ci 216ffe3c632Sopenharmony_ci private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten) 217ffe3c632Sopenharmony_ci { 218ffe3c632Sopenharmony_ci var fields = message.Descriptor.Fields; 219ffe3c632Sopenharmony_ci bool first = !assumeFirstFieldWritten; 220ffe3c632Sopenharmony_ci // First non-oneof fields 221ffe3c632Sopenharmony_ci foreach (var field in fields.InFieldNumberOrder()) 222ffe3c632Sopenharmony_ci { 223ffe3c632Sopenharmony_ci var accessor = field.Accessor; 224ffe3c632Sopenharmony_ci var value = accessor.GetValue(message); 225ffe3c632Sopenharmony_ci if (!ShouldFormatFieldValue(message, field, value)) 226ffe3c632Sopenharmony_ci { 227ffe3c632Sopenharmony_ci continue; 228ffe3c632Sopenharmony_ci } 229ffe3c632Sopenharmony_ci 230ffe3c632Sopenharmony_ci if (!first) 231ffe3c632Sopenharmony_ci { 232ffe3c632Sopenharmony_ci writer.Write(PropertySeparator); 233ffe3c632Sopenharmony_ci } 234ffe3c632Sopenharmony_ci 235ffe3c632Sopenharmony_ci WriteString(writer, accessor.Descriptor.JsonName); 236ffe3c632Sopenharmony_ci writer.Write(NameValueSeparator); 237ffe3c632Sopenharmony_ci WriteValue(writer, value); 238ffe3c632Sopenharmony_ci 239ffe3c632Sopenharmony_ci first = false; 240ffe3c632Sopenharmony_ci } 241ffe3c632Sopenharmony_ci return !first; 242ffe3c632Sopenharmony_ci } 243ffe3c632Sopenharmony_ci 244ffe3c632Sopenharmony_ci /// <summary> 245ffe3c632Sopenharmony_ci /// Determines whether or not a field value should be serialized according to the field, 246ffe3c632Sopenharmony_ci /// its value in the message, and the settings of this formatter. 247ffe3c632Sopenharmony_ci /// </summary> 248ffe3c632Sopenharmony_ci private bool ShouldFormatFieldValue(IMessage message, FieldDescriptor field, object value) => 249ffe3c632Sopenharmony_ci field.HasPresence 250ffe3c632Sopenharmony_ci // Fields that support presence *just* use that 251ffe3c632Sopenharmony_ci ? field.Accessor.HasValue(message) 252ffe3c632Sopenharmony_ci // Otherwise, format if either we've been asked to format default values, or if it's 253ffe3c632Sopenharmony_ci // not a default value anyway. 254ffe3c632Sopenharmony_ci : settings.FormatDefaultValues || !IsDefaultValue(field, value); 255ffe3c632Sopenharmony_ci 256ffe3c632Sopenharmony_ci // Converted from java/core/src/main/java/com/google/protobuf/Descriptors.java 257ffe3c632Sopenharmony_ci internal static string ToJsonName(string name) 258ffe3c632Sopenharmony_ci { 259ffe3c632Sopenharmony_ci StringBuilder result = new StringBuilder(name.Length); 260ffe3c632Sopenharmony_ci bool isNextUpperCase = false; 261ffe3c632Sopenharmony_ci foreach (char ch in name) 262ffe3c632Sopenharmony_ci { 263ffe3c632Sopenharmony_ci if (ch == '_') 264ffe3c632Sopenharmony_ci { 265ffe3c632Sopenharmony_ci isNextUpperCase = true; 266ffe3c632Sopenharmony_ci } 267ffe3c632Sopenharmony_ci else if (isNextUpperCase) 268ffe3c632Sopenharmony_ci { 269ffe3c632Sopenharmony_ci result.Append(char.ToUpperInvariant(ch)); 270ffe3c632Sopenharmony_ci isNextUpperCase = false; 271ffe3c632Sopenharmony_ci } 272ffe3c632Sopenharmony_ci else 273ffe3c632Sopenharmony_ci { 274ffe3c632Sopenharmony_ci result.Append(ch); 275ffe3c632Sopenharmony_ci } 276ffe3c632Sopenharmony_ci } 277ffe3c632Sopenharmony_ci return result.ToString(); 278ffe3c632Sopenharmony_ci } 279ffe3c632Sopenharmony_ci 280ffe3c632Sopenharmony_ci internal static string FromJsonName(string name) 281ffe3c632Sopenharmony_ci { 282ffe3c632Sopenharmony_ci StringBuilder result = new StringBuilder(name.Length); 283ffe3c632Sopenharmony_ci foreach (char ch in name) 284ffe3c632Sopenharmony_ci { 285ffe3c632Sopenharmony_ci if (char.IsUpper(ch)) 286ffe3c632Sopenharmony_ci { 287ffe3c632Sopenharmony_ci result.Append('_'); 288ffe3c632Sopenharmony_ci result.Append(char.ToLowerInvariant(ch)); 289ffe3c632Sopenharmony_ci } 290ffe3c632Sopenharmony_ci else 291ffe3c632Sopenharmony_ci { 292ffe3c632Sopenharmony_ci result.Append(ch); 293ffe3c632Sopenharmony_ci } 294ffe3c632Sopenharmony_ci } 295ffe3c632Sopenharmony_ci return result.ToString(); 296ffe3c632Sopenharmony_ci } 297ffe3c632Sopenharmony_ci 298ffe3c632Sopenharmony_ci private static void WriteNull(TextWriter writer) 299ffe3c632Sopenharmony_ci { 300ffe3c632Sopenharmony_ci writer.Write("null"); 301ffe3c632Sopenharmony_ci } 302ffe3c632Sopenharmony_ci 303ffe3c632Sopenharmony_ci private static bool IsDefaultValue(FieldDescriptor descriptor, object value) 304ffe3c632Sopenharmony_ci { 305ffe3c632Sopenharmony_ci if (descriptor.IsMap) 306ffe3c632Sopenharmony_ci { 307ffe3c632Sopenharmony_ci IDictionary dictionary = (IDictionary) value; 308ffe3c632Sopenharmony_ci return dictionary.Count == 0; 309ffe3c632Sopenharmony_ci } 310ffe3c632Sopenharmony_ci if (descriptor.IsRepeated) 311ffe3c632Sopenharmony_ci { 312ffe3c632Sopenharmony_ci IList list = (IList) value; 313ffe3c632Sopenharmony_ci return list.Count == 0; 314ffe3c632Sopenharmony_ci } 315ffe3c632Sopenharmony_ci switch (descriptor.FieldType) 316ffe3c632Sopenharmony_ci { 317ffe3c632Sopenharmony_ci case FieldType.Bool: 318ffe3c632Sopenharmony_ci return (bool) value == false; 319ffe3c632Sopenharmony_ci case FieldType.Bytes: 320ffe3c632Sopenharmony_ci return (ByteString) value == ByteString.Empty; 321ffe3c632Sopenharmony_ci case FieldType.String: 322ffe3c632Sopenharmony_ci return (string) value == ""; 323ffe3c632Sopenharmony_ci case FieldType.Double: 324ffe3c632Sopenharmony_ci return (double) value == 0.0; 325ffe3c632Sopenharmony_ci case FieldType.SInt32: 326ffe3c632Sopenharmony_ci case FieldType.Int32: 327ffe3c632Sopenharmony_ci case FieldType.SFixed32: 328ffe3c632Sopenharmony_ci case FieldType.Enum: 329ffe3c632Sopenharmony_ci return (int) value == 0; 330ffe3c632Sopenharmony_ci case FieldType.Fixed32: 331ffe3c632Sopenharmony_ci case FieldType.UInt32: 332ffe3c632Sopenharmony_ci return (uint) value == 0; 333ffe3c632Sopenharmony_ci case FieldType.Fixed64: 334ffe3c632Sopenharmony_ci case FieldType.UInt64: 335ffe3c632Sopenharmony_ci return (ulong) value == 0; 336ffe3c632Sopenharmony_ci case FieldType.SFixed64: 337ffe3c632Sopenharmony_ci case FieldType.Int64: 338ffe3c632Sopenharmony_ci case FieldType.SInt64: 339ffe3c632Sopenharmony_ci return (long) value == 0; 340ffe3c632Sopenharmony_ci case FieldType.Float: 341ffe3c632Sopenharmony_ci return (float) value == 0f; 342ffe3c632Sopenharmony_ci case FieldType.Message: 343ffe3c632Sopenharmony_ci case FieldType.Group: // Never expect to get this, but... 344ffe3c632Sopenharmony_ci return value == null; 345ffe3c632Sopenharmony_ci default: 346ffe3c632Sopenharmony_ci throw new ArgumentException("Invalid field type"); 347ffe3c632Sopenharmony_ci } 348ffe3c632Sopenharmony_ci } 349ffe3c632Sopenharmony_ci 350ffe3c632Sopenharmony_ci /// <summary> 351ffe3c632Sopenharmony_ci /// Writes a single value to the given writer as JSON. Only types understood by 352ffe3c632Sopenharmony_ci /// Protocol Buffers can be written in this way. This method is only exposed for 353ffe3c632Sopenharmony_ci /// advanced use cases; most users should be using <see cref="Format(IMessage)"/> 354ffe3c632Sopenharmony_ci /// or <see cref="Format(IMessage, TextWriter)"/>. 355ffe3c632Sopenharmony_ci /// </summary> 356ffe3c632Sopenharmony_ci /// <param name="writer">The writer to write the value to. Must not be null.</param> 357ffe3c632Sopenharmony_ci /// <param name="value">The value to write. May be null.</param> 358ffe3c632Sopenharmony_ci public void WriteValue(TextWriter writer, object value) 359ffe3c632Sopenharmony_ci { 360ffe3c632Sopenharmony_ci if (value == null || value is NullValue) 361ffe3c632Sopenharmony_ci { 362ffe3c632Sopenharmony_ci WriteNull(writer); 363ffe3c632Sopenharmony_ci } 364ffe3c632Sopenharmony_ci else if (value is bool) 365ffe3c632Sopenharmony_ci { 366ffe3c632Sopenharmony_ci writer.Write((bool)value ? "true" : "false"); 367ffe3c632Sopenharmony_ci } 368ffe3c632Sopenharmony_ci else if (value is ByteString) 369ffe3c632Sopenharmony_ci { 370ffe3c632Sopenharmony_ci // Nothing in Base64 needs escaping 371ffe3c632Sopenharmony_ci writer.Write('"'); 372ffe3c632Sopenharmony_ci writer.Write(((ByteString)value).ToBase64()); 373ffe3c632Sopenharmony_ci writer.Write('"'); 374ffe3c632Sopenharmony_ci } 375ffe3c632Sopenharmony_ci else if (value is string) 376ffe3c632Sopenharmony_ci { 377ffe3c632Sopenharmony_ci WriteString(writer, (string)value); 378ffe3c632Sopenharmony_ci } 379ffe3c632Sopenharmony_ci else if (value is IDictionary) 380ffe3c632Sopenharmony_ci { 381ffe3c632Sopenharmony_ci WriteDictionary(writer, (IDictionary)value); 382ffe3c632Sopenharmony_ci } 383ffe3c632Sopenharmony_ci else if (value is IList) 384ffe3c632Sopenharmony_ci { 385ffe3c632Sopenharmony_ci WriteList(writer, (IList)value); 386ffe3c632Sopenharmony_ci } 387ffe3c632Sopenharmony_ci else if (value is int || value is uint) 388ffe3c632Sopenharmony_ci { 389ffe3c632Sopenharmony_ci IFormattable formattable = (IFormattable) value; 390ffe3c632Sopenharmony_ci writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture)); 391ffe3c632Sopenharmony_ci } 392ffe3c632Sopenharmony_ci else if (value is long || value is ulong) 393ffe3c632Sopenharmony_ci { 394ffe3c632Sopenharmony_ci writer.Write('"'); 395ffe3c632Sopenharmony_ci IFormattable formattable = (IFormattable) value; 396ffe3c632Sopenharmony_ci writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture)); 397ffe3c632Sopenharmony_ci writer.Write('"'); 398ffe3c632Sopenharmony_ci } 399ffe3c632Sopenharmony_ci else if (value is System.Enum) 400ffe3c632Sopenharmony_ci { 401ffe3c632Sopenharmony_ci if (settings.FormatEnumsAsIntegers) 402ffe3c632Sopenharmony_ci { 403ffe3c632Sopenharmony_ci WriteValue(writer, (int)value); 404ffe3c632Sopenharmony_ci } 405ffe3c632Sopenharmony_ci else 406ffe3c632Sopenharmony_ci { 407ffe3c632Sopenharmony_ci string name = OriginalEnumValueHelper.GetOriginalName(value); 408ffe3c632Sopenharmony_ci if (name != null) 409ffe3c632Sopenharmony_ci { 410ffe3c632Sopenharmony_ci WriteString(writer, name); 411ffe3c632Sopenharmony_ci } 412ffe3c632Sopenharmony_ci else 413ffe3c632Sopenharmony_ci { 414ffe3c632Sopenharmony_ci WriteValue(writer, (int)value); 415ffe3c632Sopenharmony_ci } 416ffe3c632Sopenharmony_ci } 417ffe3c632Sopenharmony_ci } 418ffe3c632Sopenharmony_ci else if (value is float || value is double) 419ffe3c632Sopenharmony_ci { 420ffe3c632Sopenharmony_ci string text = ((IFormattable) value).ToString("r", CultureInfo.InvariantCulture); 421ffe3c632Sopenharmony_ci if (text == "NaN" || text == "Infinity" || text == "-Infinity") 422ffe3c632Sopenharmony_ci { 423ffe3c632Sopenharmony_ci writer.Write('"'); 424ffe3c632Sopenharmony_ci writer.Write(text); 425ffe3c632Sopenharmony_ci writer.Write('"'); 426ffe3c632Sopenharmony_ci } 427ffe3c632Sopenharmony_ci else 428ffe3c632Sopenharmony_ci { 429ffe3c632Sopenharmony_ci writer.Write(text); 430ffe3c632Sopenharmony_ci } 431ffe3c632Sopenharmony_ci } 432ffe3c632Sopenharmony_ci else if (value is IMessage) 433ffe3c632Sopenharmony_ci { 434ffe3c632Sopenharmony_ci Format((IMessage)value, writer); 435ffe3c632Sopenharmony_ci } 436ffe3c632Sopenharmony_ci else 437ffe3c632Sopenharmony_ci { 438ffe3c632Sopenharmony_ci throw new ArgumentException("Unable to format value of type " + value.GetType()); 439ffe3c632Sopenharmony_ci } 440ffe3c632Sopenharmony_ci } 441ffe3c632Sopenharmony_ci 442ffe3c632Sopenharmony_ci /// <summary> 443ffe3c632Sopenharmony_ci /// Central interception point for well-known type formatting. Any well-known types which 444ffe3c632Sopenharmony_ci /// don't need special handling can fall back to WriteMessage. We avoid assuming that the 445ffe3c632Sopenharmony_ci /// values are using the embedded well-known types, in order to allow for dynamic messages 446ffe3c632Sopenharmony_ci /// in the future. 447ffe3c632Sopenharmony_ci /// </summary> 448ffe3c632Sopenharmony_ci private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value) 449ffe3c632Sopenharmony_ci { 450ffe3c632Sopenharmony_ci // Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*, 451ffe3c632Sopenharmony_ci // this would do the right thing. 452ffe3c632Sopenharmony_ci if (value == null) 453ffe3c632Sopenharmony_ci { 454ffe3c632Sopenharmony_ci WriteNull(writer); 455ffe3c632Sopenharmony_ci return; 456ffe3c632Sopenharmony_ci } 457ffe3c632Sopenharmony_ci // For wrapper types, the value will either be the (possibly boxed) "native" value, 458ffe3c632Sopenharmony_ci // or the message itself if we're formatting it at the top level (e.g. just calling ToString on the object itself). 459ffe3c632Sopenharmony_ci // If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value, 460ffe3c632Sopenharmony_ci // and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string... 461ffe3c632Sopenharmony_ci // WriteValue will do the right thing.) 462ffe3c632Sopenharmony_ci if (descriptor.IsWrapperType) 463ffe3c632Sopenharmony_ci { 464ffe3c632Sopenharmony_ci if (value is IMessage) 465ffe3c632Sopenharmony_ci { 466ffe3c632Sopenharmony_ci var message = (IMessage) value; 467ffe3c632Sopenharmony_ci value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message); 468ffe3c632Sopenharmony_ci } 469ffe3c632Sopenharmony_ci WriteValue(writer, value); 470ffe3c632Sopenharmony_ci return; 471ffe3c632Sopenharmony_ci } 472ffe3c632Sopenharmony_ci if (descriptor.FullName == Timestamp.Descriptor.FullName) 473ffe3c632Sopenharmony_ci { 474ffe3c632Sopenharmony_ci WriteTimestamp(writer, (IMessage)value); 475ffe3c632Sopenharmony_ci return; 476ffe3c632Sopenharmony_ci } 477ffe3c632Sopenharmony_ci if (descriptor.FullName == Duration.Descriptor.FullName) 478ffe3c632Sopenharmony_ci { 479ffe3c632Sopenharmony_ci WriteDuration(writer, (IMessage)value); 480ffe3c632Sopenharmony_ci return; 481ffe3c632Sopenharmony_ci } 482ffe3c632Sopenharmony_ci if (descriptor.FullName == FieldMask.Descriptor.FullName) 483ffe3c632Sopenharmony_ci { 484ffe3c632Sopenharmony_ci WriteFieldMask(writer, (IMessage)value); 485ffe3c632Sopenharmony_ci return; 486ffe3c632Sopenharmony_ci } 487ffe3c632Sopenharmony_ci if (descriptor.FullName == Struct.Descriptor.FullName) 488ffe3c632Sopenharmony_ci { 489ffe3c632Sopenharmony_ci WriteStruct(writer, (IMessage)value); 490ffe3c632Sopenharmony_ci return; 491ffe3c632Sopenharmony_ci } 492ffe3c632Sopenharmony_ci if (descriptor.FullName == ListValue.Descriptor.FullName) 493ffe3c632Sopenharmony_ci { 494ffe3c632Sopenharmony_ci var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor; 495ffe3c632Sopenharmony_ci WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value)); 496ffe3c632Sopenharmony_ci return; 497ffe3c632Sopenharmony_ci } 498ffe3c632Sopenharmony_ci if (descriptor.FullName == Value.Descriptor.FullName) 499ffe3c632Sopenharmony_ci { 500ffe3c632Sopenharmony_ci WriteStructFieldValue(writer, (IMessage)value); 501ffe3c632Sopenharmony_ci return; 502ffe3c632Sopenharmony_ci } 503ffe3c632Sopenharmony_ci if (descriptor.FullName == Any.Descriptor.FullName) 504ffe3c632Sopenharmony_ci { 505ffe3c632Sopenharmony_ci WriteAny(writer, (IMessage)value); 506ffe3c632Sopenharmony_ci return; 507ffe3c632Sopenharmony_ci } 508ffe3c632Sopenharmony_ci WriteMessage(writer, (IMessage)value); 509ffe3c632Sopenharmony_ci } 510ffe3c632Sopenharmony_ci 511ffe3c632Sopenharmony_ci private void WriteTimestamp(TextWriter writer, IMessage value) 512ffe3c632Sopenharmony_ci { 513ffe3c632Sopenharmony_ci // TODO: In the common case where this *is* using the built-in Timestamp type, we could 514ffe3c632Sopenharmony_ci // avoid all the reflection at this point, by casting to Timestamp. In the interests of 515ffe3c632Sopenharmony_ci // avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove 516ffe3c632Sopenharmony_ci // it still works in that case. 517ffe3c632Sopenharmony_ci int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value); 518ffe3c632Sopenharmony_ci long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value); 519ffe3c632Sopenharmony_ci writer.Write(Timestamp.ToJson(seconds, nanos, DiagnosticOnly)); 520ffe3c632Sopenharmony_ci } 521ffe3c632Sopenharmony_ci 522ffe3c632Sopenharmony_ci private void WriteDuration(TextWriter writer, IMessage value) 523ffe3c632Sopenharmony_ci { 524ffe3c632Sopenharmony_ci // TODO: Same as for WriteTimestamp 525ffe3c632Sopenharmony_ci int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value); 526ffe3c632Sopenharmony_ci long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value); 527ffe3c632Sopenharmony_ci writer.Write(Duration.ToJson(seconds, nanos, DiagnosticOnly)); 528ffe3c632Sopenharmony_ci } 529ffe3c632Sopenharmony_ci 530ffe3c632Sopenharmony_ci private void WriteFieldMask(TextWriter writer, IMessage value) 531ffe3c632Sopenharmony_ci { 532ffe3c632Sopenharmony_ci var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value); 533ffe3c632Sopenharmony_ci writer.Write(FieldMask.ToJson(paths, DiagnosticOnly)); 534ffe3c632Sopenharmony_ci } 535ffe3c632Sopenharmony_ci 536ffe3c632Sopenharmony_ci private void WriteAny(TextWriter writer, IMessage value) 537ffe3c632Sopenharmony_ci { 538ffe3c632Sopenharmony_ci if (DiagnosticOnly) 539ffe3c632Sopenharmony_ci { 540ffe3c632Sopenharmony_ci WriteDiagnosticOnlyAny(writer, value); 541ffe3c632Sopenharmony_ci return; 542ffe3c632Sopenharmony_ci } 543ffe3c632Sopenharmony_ci 544ffe3c632Sopenharmony_ci string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value); 545ffe3c632Sopenharmony_ci ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value); 546ffe3c632Sopenharmony_ci string typeName = Any.GetTypeName(typeUrl); 547ffe3c632Sopenharmony_ci MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName); 548ffe3c632Sopenharmony_ci if (descriptor == null) 549ffe3c632Sopenharmony_ci { 550ffe3c632Sopenharmony_ci throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'"); 551ffe3c632Sopenharmony_ci } 552ffe3c632Sopenharmony_ci IMessage message = descriptor.Parser.ParseFrom(data); 553ffe3c632Sopenharmony_ci writer.Write("{ "); 554ffe3c632Sopenharmony_ci WriteString(writer, AnyTypeUrlField); 555ffe3c632Sopenharmony_ci writer.Write(NameValueSeparator); 556ffe3c632Sopenharmony_ci WriteString(writer, typeUrl); 557ffe3c632Sopenharmony_ci 558ffe3c632Sopenharmony_ci if (descriptor.IsWellKnownType) 559ffe3c632Sopenharmony_ci { 560ffe3c632Sopenharmony_ci writer.Write(PropertySeparator); 561ffe3c632Sopenharmony_ci WriteString(writer, AnyWellKnownTypeValueField); 562ffe3c632Sopenharmony_ci writer.Write(NameValueSeparator); 563ffe3c632Sopenharmony_ci WriteWellKnownTypeValue(writer, descriptor, message); 564ffe3c632Sopenharmony_ci } 565ffe3c632Sopenharmony_ci else 566ffe3c632Sopenharmony_ci { 567ffe3c632Sopenharmony_ci WriteMessageFields(writer, message, true); 568ffe3c632Sopenharmony_ci } 569ffe3c632Sopenharmony_ci writer.Write(" }"); 570ffe3c632Sopenharmony_ci } 571ffe3c632Sopenharmony_ci 572ffe3c632Sopenharmony_ci private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value) 573ffe3c632Sopenharmony_ci { 574ffe3c632Sopenharmony_ci string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value); 575ffe3c632Sopenharmony_ci ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value); 576ffe3c632Sopenharmony_ci writer.Write("{ "); 577ffe3c632Sopenharmony_ci WriteString(writer, AnyTypeUrlField); 578ffe3c632Sopenharmony_ci writer.Write(NameValueSeparator); 579ffe3c632Sopenharmony_ci WriteString(writer, typeUrl); 580ffe3c632Sopenharmony_ci writer.Write(PropertySeparator); 581ffe3c632Sopenharmony_ci WriteString(writer, AnyDiagnosticValueField); 582ffe3c632Sopenharmony_ci writer.Write(NameValueSeparator); 583ffe3c632Sopenharmony_ci writer.Write('"'); 584ffe3c632Sopenharmony_ci writer.Write(data.ToBase64()); 585ffe3c632Sopenharmony_ci writer.Write('"'); 586ffe3c632Sopenharmony_ci writer.Write(" }"); 587ffe3c632Sopenharmony_ci } 588ffe3c632Sopenharmony_ci 589ffe3c632Sopenharmony_ci private void WriteStruct(TextWriter writer, IMessage message) 590ffe3c632Sopenharmony_ci { 591ffe3c632Sopenharmony_ci writer.Write("{ "); 592ffe3c632Sopenharmony_ci IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message); 593ffe3c632Sopenharmony_ci bool first = true; 594ffe3c632Sopenharmony_ci foreach (DictionaryEntry entry in fields) 595ffe3c632Sopenharmony_ci { 596ffe3c632Sopenharmony_ci string key = (string) entry.Key; 597ffe3c632Sopenharmony_ci IMessage value = (IMessage) entry.Value; 598ffe3c632Sopenharmony_ci if (string.IsNullOrEmpty(key) || value == null) 599ffe3c632Sopenharmony_ci { 600ffe3c632Sopenharmony_ci throw new InvalidOperationException("Struct fields cannot have an empty key or a null value."); 601ffe3c632Sopenharmony_ci } 602ffe3c632Sopenharmony_ci 603ffe3c632Sopenharmony_ci if (!first) 604ffe3c632Sopenharmony_ci { 605ffe3c632Sopenharmony_ci writer.Write(PropertySeparator); 606ffe3c632Sopenharmony_ci } 607ffe3c632Sopenharmony_ci WriteString(writer, key); 608ffe3c632Sopenharmony_ci writer.Write(NameValueSeparator); 609ffe3c632Sopenharmony_ci WriteStructFieldValue(writer, value); 610ffe3c632Sopenharmony_ci first = false; 611ffe3c632Sopenharmony_ci } 612ffe3c632Sopenharmony_ci writer.Write(first ? "}" : " }"); 613ffe3c632Sopenharmony_ci } 614ffe3c632Sopenharmony_ci 615ffe3c632Sopenharmony_ci private void WriteStructFieldValue(TextWriter writer, IMessage message) 616ffe3c632Sopenharmony_ci { 617ffe3c632Sopenharmony_ci var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message); 618ffe3c632Sopenharmony_ci if (specifiedField == null) 619ffe3c632Sopenharmony_ci { 620ffe3c632Sopenharmony_ci throw new InvalidOperationException("Value message must contain a value for the oneof."); 621ffe3c632Sopenharmony_ci } 622ffe3c632Sopenharmony_ci 623ffe3c632Sopenharmony_ci object value = specifiedField.Accessor.GetValue(message); 624ffe3c632Sopenharmony_ci 625ffe3c632Sopenharmony_ci switch (specifiedField.FieldNumber) 626ffe3c632Sopenharmony_ci { 627ffe3c632Sopenharmony_ci case Value.BoolValueFieldNumber: 628ffe3c632Sopenharmony_ci case Value.StringValueFieldNumber: 629ffe3c632Sopenharmony_ci case Value.NumberValueFieldNumber: 630ffe3c632Sopenharmony_ci WriteValue(writer, value); 631ffe3c632Sopenharmony_ci return; 632ffe3c632Sopenharmony_ci case Value.StructValueFieldNumber: 633ffe3c632Sopenharmony_ci case Value.ListValueFieldNumber: 634ffe3c632Sopenharmony_ci // Structs and ListValues are nested messages, and already well-known types. 635ffe3c632Sopenharmony_ci var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message); 636ffe3c632Sopenharmony_ci WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage); 637ffe3c632Sopenharmony_ci return; 638ffe3c632Sopenharmony_ci case Value.NullValueFieldNumber: 639ffe3c632Sopenharmony_ci WriteNull(writer); 640ffe3c632Sopenharmony_ci return; 641ffe3c632Sopenharmony_ci default: 642ffe3c632Sopenharmony_ci throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber); 643ffe3c632Sopenharmony_ci } 644ffe3c632Sopenharmony_ci } 645ffe3c632Sopenharmony_ci 646ffe3c632Sopenharmony_ci internal void WriteList(TextWriter writer, IList list) 647ffe3c632Sopenharmony_ci { 648ffe3c632Sopenharmony_ci writer.Write("[ "); 649ffe3c632Sopenharmony_ci bool first = true; 650ffe3c632Sopenharmony_ci foreach (var value in list) 651ffe3c632Sopenharmony_ci { 652ffe3c632Sopenharmony_ci if (!first) 653ffe3c632Sopenharmony_ci { 654ffe3c632Sopenharmony_ci writer.Write(PropertySeparator); 655ffe3c632Sopenharmony_ci } 656ffe3c632Sopenharmony_ci WriteValue(writer, value); 657ffe3c632Sopenharmony_ci first = false; 658ffe3c632Sopenharmony_ci } 659ffe3c632Sopenharmony_ci writer.Write(first ? "]" : " ]"); 660ffe3c632Sopenharmony_ci } 661ffe3c632Sopenharmony_ci 662ffe3c632Sopenharmony_ci internal void WriteDictionary(TextWriter writer, IDictionary dictionary) 663ffe3c632Sopenharmony_ci { 664ffe3c632Sopenharmony_ci writer.Write("{ "); 665ffe3c632Sopenharmony_ci bool first = true; 666ffe3c632Sopenharmony_ci // This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal. 667ffe3c632Sopenharmony_ci foreach (DictionaryEntry pair in dictionary) 668ffe3c632Sopenharmony_ci { 669ffe3c632Sopenharmony_ci if (!first) 670ffe3c632Sopenharmony_ci { 671ffe3c632Sopenharmony_ci writer.Write(PropertySeparator); 672ffe3c632Sopenharmony_ci } 673ffe3c632Sopenharmony_ci string keyText; 674ffe3c632Sopenharmony_ci if (pair.Key is string) 675ffe3c632Sopenharmony_ci { 676ffe3c632Sopenharmony_ci keyText = (string) pair.Key; 677ffe3c632Sopenharmony_ci } 678ffe3c632Sopenharmony_ci else if (pair.Key is bool) 679ffe3c632Sopenharmony_ci { 680ffe3c632Sopenharmony_ci keyText = (bool) pair.Key ? "true" : "false"; 681ffe3c632Sopenharmony_ci } 682ffe3c632Sopenharmony_ci else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong) 683ffe3c632Sopenharmony_ci { 684ffe3c632Sopenharmony_ci keyText = ((IFormattable) pair.Key).ToString("d", CultureInfo.InvariantCulture); 685ffe3c632Sopenharmony_ci } 686ffe3c632Sopenharmony_ci else 687ffe3c632Sopenharmony_ci { 688ffe3c632Sopenharmony_ci if (pair.Key == null) 689ffe3c632Sopenharmony_ci { 690ffe3c632Sopenharmony_ci throw new ArgumentException("Dictionary has entry with null key"); 691ffe3c632Sopenharmony_ci } 692ffe3c632Sopenharmony_ci throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType()); 693ffe3c632Sopenharmony_ci } 694ffe3c632Sopenharmony_ci WriteString(writer, keyText); 695ffe3c632Sopenharmony_ci writer.Write(NameValueSeparator); 696ffe3c632Sopenharmony_ci WriteValue(writer, pair.Value); 697ffe3c632Sopenharmony_ci first = false; 698ffe3c632Sopenharmony_ci } 699ffe3c632Sopenharmony_ci writer.Write(first ? "}" : " }"); 700ffe3c632Sopenharmony_ci } 701ffe3c632Sopenharmony_ci 702ffe3c632Sopenharmony_ci /// <summary> 703ffe3c632Sopenharmony_ci /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required. 704ffe3c632Sopenharmony_ci /// </summary> 705ffe3c632Sopenharmony_ci /// <remarks> 706ffe3c632Sopenharmony_ci /// Other than surrogate pair handling, this code is mostly taken from src/google/protobuf/util/internal/json_escaping.cc. 707ffe3c632Sopenharmony_ci /// </remarks> 708ffe3c632Sopenharmony_ci internal static void WriteString(TextWriter writer, string text) 709ffe3c632Sopenharmony_ci { 710ffe3c632Sopenharmony_ci writer.Write('"'); 711ffe3c632Sopenharmony_ci for (int i = 0; i < text.Length; i++) 712ffe3c632Sopenharmony_ci { 713ffe3c632Sopenharmony_ci char c = text[i]; 714ffe3c632Sopenharmony_ci if (c < 0xa0) 715ffe3c632Sopenharmony_ci { 716ffe3c632Sopenharmony_ci writer.Write(CommonRepresentations[c]); 717ffe3c632Sopenharmony_ci continue; 718ffe3c632Sopenharmony_ci } 719ffe3c632Sopenharmony_ci if (char.IsHighSurrogate(c)) 720ffe3c632Sopenharmony_ci { 721ffe3c632Sopenharmony_ci // Encountered first part of a surrogate pair. 722ffe3c632Sopenharmony_ci // Check that we have the whole pair, and encode both parts as hex. 723ffe3c632Sopenharmony_ci i++; 724ffe3c632Sopenharmony_ci if (i == text.Length || !char.IsLowSurrogate(text[i])) 725ffe3c632Sopenharmony_ci { 726ffe3c632Sopenharmony_ci throw new ArgumentException("String contains low surrogate not followed by high surrogate"); 727ffe3c632Sopenharmony_ci } 728ffe3c632Sopenharmony_ci HexEncodeUtf16CodeUnit(writer, c); 729ffe3c632Sopenharmony_ci HexEncodeUtf16CodeUnit(writer, text[i]); 730ffe3c632Sopenharmony_ci continue; 731ffe3c632Sopenharmony_ci } 732ffe3c632Sopenharmony_ci else if (char.IsLowSurrogate(c)) 733ffe3c632Sopenharmony_ci { 734ffe3c632Sopenharmony_ci throw new ArgumentException("String contains high surrogate not preceded by low surrogate"); 735ffe3c632Sopenharmony_ci } 736ffe3c632Sopenharmony_ci switch ((uint) c) 737ffe3c632Sopenharmony_ci { 738ffe3c632Sopenharmony_ci // These are not required by json spec 739ffe3c632Sopenharmony_ci // but used to prevent security bugs in javascript. 740ffe3c632Sopenharmony_ci case 0xfeff: // Zero width no-break space 741ffe3c632Sopenharmony_ci case 0xfff9: // Interlinear annotation anchor 742ffe3c632Sopenharmony_ci case 0xfffa: // Interlinear annotation separator 743ffe3c632Sopenharmony_ci case 0xfffb: // Interlinear annotation terminator 744ffe3c632Sopenharmony_ci 745ffe3c632Sopenharmony_ci case 0x00ad: // Soft-hyphen 746ffe3c632Sopenharmony_ci case 0x06dd: // Arabic end of ayah 747ffe3c632Sopenharmony_ci case 0x070f: // Syriac abbreviation mark 748ffe3c632Sopenharmony_ci case 0x17b4: // Khmer vowel inherent Aq 749ffe3c632Sopenharmony_ci case 0x17b5: // Khmer vowel inherent Aa 750ffe3c632Sopenharmony_ci HexEncodeUtf16CodeUnit(writer, c); 751ffe3c632Sopenharmony_ci break; 752ffe3c632Sopenharmony_ci 753ffe3c632Sopenharmony_ci default: 754ffe3c632Sopenharmony_ci if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs 755ffe3c632Sopenharmony_ci (c >= 0x200b && c <= 0x200f) || // Zero width etc. 756ffe3c632Sopenharmony_ci (c >= 0x2028 && c <= 0x202e) || // Separators etc. 757ffe3c632Sopenharmony_ci (c >= 0x2060 && c <= 0x2064) || // Invisible etc. 758ffe3c632Sopenharmony_ci (c >= 0x206a && c <= 0x206f)) 759ffe3c632Sopenharmony_ci { 760ffe3c632Sopenharmony_ci HexEncodeUtf16CodeUnit(writer, c); 761ffe3c632Sopenharmony_ci } 762ffe3c632Sopenharmony_ci else 763ffe3c632Sopenharmony_ci { 764ffe3c632Sopenharmony_ci // No handling of surrogates here - that's done earlier 765ffe3c632Sopenharmony_ci writer.Write(c); 766ffe3c632Sopenharmony_ci } 767ffe3c632Sopenharmony_ci break; 768ffe3c632Sopenharmony_ci } 769ffe3c632Sopenharmony_ci } 770ffe3c632Sopenharmony_ci writer.Write('"'); 771ffe3c632Sopenharmony_ci } 772ffe3c632Sopenharmony_ci 773ffe3c632Sopenharmony_ci private const string Hex = "0123456789abcdef"; 774ffe3c632Sopenharmony_ci private static void HexEncodeUtf16CodeUnit(TextWriter writer, char c) 775ffe3c632Sopenharmony_ci { 776ffe3c632Sopenharmony_ci writer.Write("\\u"); 777ffe3c632Sopenharmony_ci writer.Write(Hex[(c >> 12) & 0xf]); 778ffe3c632Sopenharmony_ci writer.Write(Hex[(c >> 8) & 0xf]); 779ffe3c632Sopenharmony_ci writer.Write(Hex[(c >> 4) & 0xf]); 780ffe3c632Sopenharmony_ci writer.Write(Hex[(c >> 0) & 0xf]); 781ffe3c632Sopenharmony_ci } 782ffe3c632Sopenharmony_ci 783ffe3c632Sopenharmony_ci /// <summary> 784ffe3c632Sopenharmony_ci /// Settings controlling JSON formatting. 785ffe3c632Sopenharmony_ci /// </summary> 786ffe3c632Sopenharmony_ci public sealed class Settings 787ffe3c632Sopenharmony_ci { 788ffe3c632Sopenharmony_ci /// <summary> 789ffe3c632Sopenharmony_ci /// Default settings, as used by <see cref="JsonFormatter.Default"/> 790ffe3c632Sopenharmony_ci /// </summary> 791ffe3c632Sopenharmony_ci public static Settings Default { get; } 792ffe3c632Sopenharmony_ci 793ffe3c632Sopenharmony_ci // Workaround for the Mono compiler complaining about XML comments not being on 794ffe3c632Sopenharmony_ci // valid language elements. 795ffe3c632Sopenharmony_ci static Settings() 796ffe3c632Sopenharmony_ci { 797ffe3c632Sopenharmony_ci Default = new Settings(false); 798ffe3c632Sopenharmony_ci } 799ffe3c632Sopenharmony_ci 800ffe3c632Sopenharmony_ci /// <summary> 801ffe3c632Sopenharmony_ci /// Whether fields which would otherwise not be included in the formatted data 802ffe3c632Sopenharmony_ci /// should be formatted even when the value is not present, or has the default value. 803ffe3c632Sopenharmony_ci /// This option only affects fields which don't support "presence" (e.g. 804ffe3c632Sopenharmony_ci /// singular non-optional proto3 primitive fields). 805ffe3c632Sopenharmony_ci /// </summary> 806ffe3c632Sopenharmony_ci public bool FormatDefaultValues { get; } 807ffe3c632Sopenharmony_ci 808ffe3c632Sopenharmony_ci /// <summary> 809ffe3c632Sopenharmony_ci /// The type registry used to format <see cref="Any"/> messages. 810ffe3c632Sopenharmony_ci /// </summary> 811ffe3c632Sopenharmony_ci public TypeRegistry TypeRegistry { get; } 812ffe3c632Sopenharmony_ci 813ffe3c632Sopenharmony_ci /// <summary> 814ffe3c632Sopenharmony_ci /// Whether to format enums as ints. Defaults to false. 815ffe3c632Sopenharmony_ci /// </summary> 816ffe3c632Sopenharmony_ci public bool FormatEnumsAsIntegers { get; } 817ffe3c632Sopenharmony_ci 818ffe3c632Sopenharmony_ci 819ffe3c632Sopenharmony_ci /// <summary> 820ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified formatting of default values 821ffe3c632Sopenharmony_ci /// and an empty type registry. 822ffe3c632Sopenharmony_ci /// </summary> 823ffe3c632Sopenharmony_ci /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param> 824ffe3c632Sopenharmony_ci public Settings(bool formatDefaultValues) : this(formatDefaultValues, TypeRegistry.Empty) 825ffe3c632Sopenharmony_ci { 826ffe3c632Sopenharmony_ci } 827ffe3c632Sopenharmony_ci 828ffe3c632Sopenharmony_ci /// <summary> 829ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified formatting of default values 830ffe3c632Sopenharmony_ci /// and type registry. 831ffe3c632Sopenharmony_ci /// </summary> 832ffe3c632Sopenharmony_ci /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param> 833ffe3c632Sopenharmony_ci /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param> 834ffe3c632Sopenharmony_ci public Settings(bool formatDefaultValues, TypeRegistry typeRegistry) : this(formatDefaultValues, typeRegistry, false) 835ffe3c632Sopenharmony_ci { 836ffe3c632Sopenharmony_ci } 837ffe3c632Sopenharmony_ci 838ffe3c632Sopenharmony_ci /// <summary> 839ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified parameters. 840ffe3c632Sopenharmony_ci /// </summary> 841ffe3c632Sopenharmony_ci /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param> 842ffe3c632Sopenharmony_ci /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages. TypeRegistry.Empty will be used if it is null.</param> 843ffe3c632Sopenharmony_ci /// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param> 844ffe3c632Sopenharmony_ci private Settings(bool formatDefaultValues, 845ffe3c632Sopenharmony_ci TypeRegistry typeRegistry, 846ffe3c632Sopenharmony_ci bool formatEnumsAsIntegers) 847ffe3c632Sopenharmony_ci { 848ffe3c632Sopenharmony_ci FormatDefaultValues = formatDefaultValues; 849ffe3c632Sopenharmony_ci TypeRegistry = typeRegistry ?? TypeRegistry.Empty; 850ffe3c632Sopenharmony_ci FormatEnumsAsIntegers = formatEnumsAsIntegers; 851ffe3c632Sopenharmony_ci } 852ffe3c632Sopenharmony_ci 853ffe3c632Sopenharmony_ci /// <summary> 854ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified formatting of default values and the current settings. 855ffe3c632Sopenharmony_ci /// </summary> 856ffe3c632Sopenharmony_ci /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param> 857ffe3c632Sopenharmony_ci public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers); 858ffe3c632Sopenharmony_ci 859ffe3c632Sopenharmony_ci /// <summary> 860ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified type registry and the current settings. 861ffe3c632Sopenharmony_ci /// </summary> 862ffe3c632Sopenharmony_ci /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param> 863ffe3c632Sopenharmony_ci public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers); 864ffe3c632Sopenharmony_ci 865ffe3c632Sopenharmony_ci /// <summary> 866ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified enums formatting option and the current settings. 867ffe3c632Sopenharmony_ci /// </summary> 868ffe3c632Sopenharmony_ci /// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param> 869ffe3c632Sopenharmony_ci public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers); 870ffe3c632Sopenharmony_ci } 871ffe3c632Sopenharmony_ci 872ffe3c632Sopenharmony_ci // Effectively a cache of mapping from enum values to the original name as specified in the proto file, 873ffe3c632Sopenharmony_ci // fetched by reflection. 874ffe3c632Sopenharmony_ci // The need for this is unfortunate, as is its unbounded size, but realistically it shouldn't cause issues. 875ffe3c632Sopenharmony_ci private static class OriginalEnumValueHelper 876ffe3c632Sopenharmony_ci { 877ffe3c632Sopenharmony_ci // TODO: In the future we might want to use ConcurrentDictionary, at the point where all 878ffe3c632Sopenharmony_ci // the platforms we target have it. 879ffe3c632Sopenharmony_ci private static readonly Dictionary<System.Type, Dictionary<object, string>> dictionaries 880ffe3c632Sopenharmony_ci = new Dictionary<System.Type, Dictionary<object, string>>(); 881ffe3c632Sopenharmony_ci 882ffe3c632Sopenharmony_ci internal static string GetOriginalName(object value) 883ffe3c632Sopenharmony_ci { 884ffe3c632Sopenharmony_ci var enumType = value.GetType(); 885ffe3c632Sopenharmony_ci Dictionary<object, string> nameMapping; 886ffe3c632Sopenharmony_ci lock (dictionaries) 887ffe3c632Sopenharmony_ci { 888ffe3c632Sopenharmony_ci if (!dictionaries.TryGetValue(enumType, out nameMapping)) 889ffe3c632Sopenharmony_ci { 890ffe3c632Sopenharmony_ci nameMapping = GetNameMapping(enumType); 891ffe3c632Sopenharmony_ci dictionaries[enumType] = nameMapping; 892ffe3c632Sopenharmony_ci } 893ffe3c632Sopenharmony_ci } 894ffe3c632Sopenharmony_ci 895ffe3c632Sopenharmony_ci string originalName; 896ffe3c632Sopenharmony_ci // If this returns false, originalName will be null, which is what we want. 897ffe3c632Sopenharmony_ci nameMapping.TryGetValue(value, out originalName); 898ffe3c632Sopenharmony_ci return originalName; 899ffe3c632Sopenharmony_ci } 900ffe3c632Sopenharmony_ci 901ffe3c632Sopenharmony_ci#if NET35 902ffe3c632Sopenharmony_ci // TODO: Consider adding functionality to TypeExtensions to avoid this difference. 903ffe3c632Sopenharmony_ci private static Dictionary<object, string> GetNameMapping(System.Type enumType) => 904ffe3c632Sopenharmony_ci enumType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) 905ffe3c632Sopenharmony_ci .Where(f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false) 906ffe3c632Sopenharmony_ci .FirstOrDefault() as OriginalNameAttribute) 907ffe3c632Sopenharmony_ci ?.PreferredAlias ?? true) 908ffe3c632Sopenharmony_ci .ToDictionary(f => f.GetValue(null), 909ffe3c632Sopenharmony_ci f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false) 910ffe3c632Sopenharmony_ci .FirstOrDefault() as OriginalNameAttribute) 911ffe3c632Sopenharmony_ci // If the attribute hasn't been applied, fall back to the name of the field. 912ffe3c632Sopenharmony_ci ?.Name ?? f.Name); 913ffe3c632Sopenharmony_ci#else 914ffe3c632Sopenharmony_ci private static Dictionary<object, string> GetNameMapping(System.Type enumType) => 915ffe3c632Sopenharmony_ci enumType.GetTypeInfo().DeclaredFields 916ffe3c632Sopenharmony_ci .Where(f => f.IsStatic) 917ffe3c632Sopenharmony_ci .Where(f => f.GetCustomAttributes<OriginalNameAttribute>() 918ffe3c632Sopenharmony_ci .FirstOrDefault()?.PreferredAlias ?? true) 919ffe3c632Sopenharmony_ci .ToDictionary(f => f.GetValue(null), 920ffe3c632Sopenharmony_ci f => f.GetCustomAttributes<OriginalNameAttribute>() 921ffe3c632Sopenharmony_ci .FirstOrDefault() 922ffe3c632Sopenharmony_ci // If the attribute hasn't been applied, fall back to the name of the field. 923ffe3c632Sopenharmony_ci ?.Name ?? f.Name); 924ffe3c632Sopenharmony_ci#endif 925ffe3c632Sopenharmony_ci } 926ffe3c632Sopenharmony_ci } 927ffe3c632Sopenharmony_ci} 928