xref: /aosp_15_r20/external/protobuf/csharp/src/Google.Protobuf/JsonFormatter.cs (revision 1b3f573f81763fcece89efc2b6a5209149e44ab8)
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