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