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