blob: cbd9366c34f1cc82dce07686741458d8fecc8865 [file] [log] [blame]
Jon Skeetf8c151f2015-07-03 11:56:29 +01001#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
33using System;
34using System.Collections;
35using System.Globalization;
36using System.Text;
Jon Skeet9f37de92015-07-14 10:24:52 +010037using Google.Protobuf.Reflection;
Jon Skeetc9fd53a2015-07-20 11:48:24 +010038using Google.Protobuf.WellKnownTypes;
avgwebad2d7752016-03-06 17:50:02 -080039using System.IO;
Jon Skeet0e30de32015-08-03 11:43:07 +010040using System.Linq;
Jon Skeetdd43dcc2016-01-20 18:43:00 +000041using System.Collections.Generic;
Jon Skeetf8c151f2015-07-03 11:56:29 +010042
43namespace Google.Protobuf
44{
45 /// <summary>
46 /// Reflection-based converter from messages to JSON.
47 /// </summary>
48 /// <remarks>
49 /// <para>
50 /// Instances of this class are thread-safe, with no mutable state.
51 /// </para>
52 /// <para>
53 /// This is a simple start to get JSON formatting working. As it's reflection-based,
54 /// it's not as quick as baking calls into generated messages - but is a simpler implementation.
55 /// (This code is generally not heavily optimized.)
56 /// </para>
57 /// </remarks>
58 public sealed class JsonFormatter
59 {
Jon Skeet567579b2015-11-23 12:43:54 +000060 internal const string AnyTypeUrlField = "@type";
Jon Skeetaabc6c42015-12-15 09:23:38 +000061 internal const string AnyDiagnosticValueField = "@value";
Jon Skeet567579b2015-11-23 12:43:54 +000062 internal const string AnyWellKnownTypeValueField = "value";
63 private const string TypeUrlPrefix = "type.googleapis.com";
64 private const string NameValueSeparator = ": ";
65 private const string PropertySeparator = ", ";
66
Jon Skeetf8c151f2015-07-03 11:56:29 +010067 /// <summary>
68 /// Returns a formatter using the default settings.
69 /// </summary>
Jon Skeetaabc6c42015-12-15 09:23:38 +000070 public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default);
71
72 // A JSON formatter which *only* exists
73 private static readonly JsonFormatter diagnosticFormatter = new JsonFormatter(Settings.Default);
Jon Skeetf8c151f2015-07-03 11:56:29 +010074
75 /// <summary>
76 /// The JSON representation of the first 160 characters of Unicode.
77 /// Empty strings are replaced by the static constructor.
78 /// </summary>
79 private static readonly string[] CommonRepresentations = {
80 // C0 (ASCII and derivatives) control characters
81 "\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00
82 "\\u0004", "\\u0005", "\\u0006", "\\u0007",
83 "\\b", "\\t", "\\n", "\\u000b",
84 "\\f", "\\r", "\\u000e", "\\u000f",
85 "\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10
86 "\\u0014", "\\u0015", "\\u0016", "\\u0017",
87 "\\u0018", "\\u0019", "\\u001a", "\\u001b",
88 "\\u001c", "\\u001d", "\\u001e", "\\u001f",
89 // Escaping of " and \ are required by www.json.org string definition.
90 // Escaping of < and > are required for HTML security.
91 "", "", "\\\"", "", "", "", "", "", // 0x20
92 "", "", "", "", "", "", "", "",
93 "", "", "", "", "", "", "", "", // 0x30
94 "", "", "", "", "\\u003c", "", "\\u003e", "",
95 "", "", "", "", "", "", "", "", // 0x40
96 "", "", "", "", "", "", "", "",
97 "", "", "", "", "", "", "", "", // 0x50
98 "", "", "", "", "\\\\", "", "", "",
99 "", "", "", "", "", "", "", "", // 0x60
100 "", "", "", "", "", "", "", "",
101 "", "", "", "", "", "", "", "", // 0x70
102 "", "", "", "", "", "", "", "\\u007f",
103 // C1 (ISO 8859 and Unicode) extended control characters
104 "\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80
105 "\\u0084", "\\u0085", "\\u0086", "\\u0087",
106 "\\u0088", "\\u0089", "\\u008a", "\\u008b",
107 "\\u008c", "\\u008d", "\\u008e", "\\u008f",
108 "\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90
109 "\\u0094", "\\u0095", "\\u0096", "\\u0097",
110 "\\u0098", "\\u0099", "\\u009a", "\\u009b",
111 "\\u009c", "\\u009d", "\\u009e", "\\u009f"
112 };
113
114 static JsonFormatter()
115 {
116 for (int i = 0; i < CommonRepresentations.Length; i++)
117 {
118 if (CommonRepresentations[i] == "")
119 {
120 CommonRepresentations[i] = ((char) i).ToString();
121 }
122 }
123 }
124
125 private readonly Settings settings;
126
Jon Skeetdd43dcc2016-01-20 18:43:00 +0000127 private bool DiagnosticOnly => ReferenceEquals(this, diagnosticFormatter);
128
Jon Skeet811fc892015-08-04 15:58:39 +0100129 /// <summary>
130 /// Creates a new formatted with the given settings.
131 /// </summary>
132 /// <param name="settings">The settings.</param>
Jon Skeetf8c151f2015-07-03 11:56:29 +0100133 public JsonFormatter(Settings settings)
134 {
135 this.settings = settings;
136 }
137
Jon Skeet811fc892015-08-04 15:58:39 +0100138 /// <summary>
139 /// Formats the specified message as JSON.
140 /// </summary>
141 /// <param name="message">The message to format.</param>
142 /// <returns>The formatted message.</returns>
Jon Skeet53c399a2015-07-20 19:24:31 +0100143 public string Format(IMessage message)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100144 {
avgwebad2d7752016-03-06 17:50:02 -0800145 var writer = new StringWriter();
146 Format(message, writer);
147 return writer.ToString();
148 }
149
150 /// <summary>
151 /// Formats the specified message as JSON.
152 /// </summary>
153 /// <param name="message">The message to format.</param>
154 /// <param name="writer">The TextWriter to write the formatted message to.</param>
155 /// <returns>The formatted message.</returns>
156 public void Format(IMessage message, TextWriter writer)
157 {
Jon Skeet7762f162016-02-04 06:51:54 +0000158 ProtoPreconditions.CheckNotNull(message, nameof(message));
avgwebad2d7752016-03-06 17:50:02 -0800159 ProtoPreconditions.CheckNotNull(writer, nameof(writer));
160
Jon Skeet16e272e2015-07-31 13:22:15 +0100161 if (message.Descriptor.IsWellKnownType)
162 {
avgwebad2d7752016-03-06 17:50:02 -0800163 WriteWellKnownTypeValue(writer, message.Descriptor, message);
Jon Skeet16e272e2015-07-31 13:22:15 +0100164 }
165 else
166 {
avgwebad2d7752016-03-06 17:50:02 -0800167 WriteMessage(writer, message);
Jon Skeet16e272e2015-07-31 13:22:15 +0100168 }
Jon Skeetf8c151f2015-07-03 11:56:29 +0100169 }
170
Jon Skeetaabc6c42015-12-15 09:23:38 +0000171 /// <summary>
172 /// Converts a message to JSON for diagnostic purposes with no extra context.
173 /// </summary>
174 /// <remarks>
175 /// <para>
176 /// This differs from calling <see cref="Format(IMessage)"/> on the default JSON
177 /// formatter in its handling of <see cref="Any"/>. As no type registry is available
178 /// in <see cref="object.ToString"/> calls, the normal way of resolving the type of
179 /// an <c>Any</c> message cannot be applied. Instead, a JSON property named <c>@value</c>
180 /// is included with the base64 data from the <see cref="Any.Value"/> property of the message.
181 /// </para>
182 /// <para>The value returned by this method is only designed to be used for diagnostic
183 /// purposes. It may not be parsable by <see cref="JsonParser"/>, and may not be parsable
184 /// by other Protocol Buffer implementations.</para>
185 /// </remarks>
186 /// <param name="message">The message to format for diagnostic purposes.</param>
187 /// <returns>The diagnostic-only JSON representation of the message</returns>
188 public static string ToDiagnosticString(IMessage message)
189 {
Jon Skeet7762f162016-02-04 06:51:54 +0000190 ProtoPreconditions.CheckNotNull(message, nameof(message));
Jon Skeetaabc6c42015-12-15 09:23:38 +0000191 return diagnosticFormatter.Format(message);
192 }
193
avgwebad2d7752016-03-06 17:50:02 -0800194 private void WriteMessage(TextWriter writer, IMessage message)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100195 {
196 if (message == null)
197 {
avgwebad2d7752016-03-06 17:50:02 -0800198 WriteNull(writer);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100199 return;
200 }
Jon Skeetdd43dcc2016-01-20 18:43:00 +0000201 if (DiagnosticOnly)
Jon Skeet5dba7d72016-01-06 11:12:56 +0000202 {
203 ICustomDiagnosticMessage customDiagnosticMessage = message as ICustomDiagnosticMessage;
204 if (customDiagnosticMessage != null)
205 {
avgwebad2d7752016-03-06 17:50:02 -0800206 writer.Write(customDiagnosticMessage.ToDiagnosticString());
Jon Skeet5dba7d72016-01-06 11:12:56 +0000207 return;
208 }
209 }
avgwebad2d7752016-03-06 17:50:02 -0800210 writer.Write("{ ");
211 bool writtenFields = WriteMessageFields(writer, message, false);
212 writer.Write(writtenFields ? " }" : "}");
Jon Skeet567579b2015-11-23 12:43:54 +0000213 }
214
avgwebad2d7752016-03-06 17:50:02 -0800215 private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten)
Jon Skeet567579b2015-11-23 12:43:54 +0000216 {
Jon Skeet53c399a2015-07-20 19:24:31 +0100217 var fields = message.Descriptor.Fields;
Jon Skeet567579b2015-11-23 12:43:54 +0000218 bool first = !assumeFirstFieldWritten;
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100219 // First non-oneof fields
Jon Skeetc1c6b2d2015-07-22 19:57:29 +0100220 foreach (var field in fields.InFieldNumberOrder())
Jon Skeetf8c151f2015-07-03 11:56:29 +0100221 {
Jon Skeet53c399a2015-07-20 19:24:31 +0100222 var accessor = field.Accessor;
Jon Skeet4fed0b52015-07-31 10:33:31 +0100223 if (field.ContainingOneof != null && field.ContainingOneof.Accessor.GetCaseFieldDescriptor(message) != field)
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100224 {
225 continue;
226 }
Jon Skeet4fed0b52015-07-31 10:33:31 +0100227 // Omit default values unless we're asked to format them, or they're oneofs (where the default
228 // value is still formatted regardless, because that's how we preserve the oneof case).
Jon Skeetf8c151f2015-07-03 11:56:29 +0100229 object value = accessor.GetValue(message);
Jon Skeet4fed0b52015-07-31 10:33:31 +0100230 if (field.ContainingOneof == null && !settings.FormatDefaultValues && IsDefaultValue(accessor, value))
Jon Skeetf8c151f2015-07-03 11:56:29 +0100231 {
232 continue;
233 }
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100234
235 // Okay, all tests complete: let's write the field value...
Jon Skeetf8c151f2015-07-03 11:56:29 +0100236 if (!first)
237 {
avgwebad2d7752016-03-06 17:50:02 -0800238 writer.Write(PropertySeparator);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100239 }
alien0d32ab32016-03-17 15:19:04 +0300240
alien6f8dd212016-03-29 20:56:32 +0300241 WriteString(writer, accessor.Descriptor.JsonName);
avgwebad2d7752016-03-06 17:50:02 -0800242 writer.Write(NameValueSeparator);
243 WriteValue(writer, value);
alien0d32ab32016-03-17 15:19:04 +0300244
Jon Skeetf8c151f2015-07-03 11:56:29 +0100245 first = false;
alien0d32ab32016-03-17 15:19:04 +0300246 }
Jon Skeet567579b2015-11-23 12:43:54 +0000247 return !first;
Jon Skeetf8c151f2015-07-03 11:56:29 +0100248 }
249
Jon Skeetf437b672016-01-15 12:02:07 +0000250 /// <summary>
251 /// Camel-case converter with added strictness for field mask formatting.
252 /// </summary>
253 /// <exception cref="InvalidOperationException">The field mask is invalid for JSON representation</exception>
254 private static string ToCamelCaseForFieldMask(string input)
255 {
256 for (int i = 0; i < input.Length; i++)
257 {
258 char c = input[i];
259 if (c >= 'A' && c <= 'Z')
260 {
261 throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
262 }
263 if (c == '_' && i < input.Length - 1)
264 {
265 char next = input[i + 1];
266 if (next < 'a' || next > 'z')
267 {
268 throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
269 }
270 }
271 }
272 return ToCamelCase(input);
273 }
274
Jon Skeetf8c151f2015-07-03 11:56:29 +0100275 // Converted from src/google/protobuf/util/internal/utility.cc ToCamelCase
Jon Skeetfb248822015-09-04 12:41:14 +0100276 // TODO: Use the new field in FieldDescriptor.
Jon Skeetf8c151f2015-07-03 11:56:29 +0100277 internal static string ToCamelCase(string input)
278 {
279 bool capitalizeNext = false;
280 bool wasCap = true;
281 bool isCap = false;
282 bool firstWord = true;
283 StringBuilder result = new StringBuilder(input.Length);
284
285 for (int i = 0; i < input.Length; i++, wasCap = isCap)
286 {
287 isCap = char.IsUpper(input[i]);
288 if (input[i] == '_')
289 {
290 capitalizeNext = true;
291 if (result.Length != 0)
292 {
293 firstWord = false;
294 }
295 continue;
296 }
297 else if (firstWord)
298 {
299 // Consider when the current character B is capitalized,
300 // first word ends when:
301 // 1) following a lowercase: "...aB..."
302 // 2) followed by a lowercase: "...ABc..."
303 if (result.Length != 0 && isCap &&
304 (!wasCap || (i + 1 < input.Length && char.IsLower(input[i + 1]))))
305 {
306 firstWord = false;
307 }
308 else
309 {
310 result.Append(char.ToLowerInvariant(input[i]));
311 continue;
312 }
313 }
314 else if (capitalizeNext)
315 {
316 capitalizeNext = false;
317 if (char.IsLower(input[i]))
318 {
319 result.Append(char.ToUpperInvariant(input[i]));
320 continue;
321 }
322 }
323 result.Append(input[i]);
324 }
325 return result.ToString();
326 }
327
avgwebad2d7752016-03-06 17:50:02 -0800328 private static void WriteNull(TextWriter writer)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100329 {
avgwebad2d7752016-03-06 17:50:02 -0800330 writer.Write("null");
Jon Skeetf8c151f2015-07-03 11:56:29 +0100331 }
332
333 private static bool IsDefaultValue(IFieldAccessor accessor, object value)
334 {
335 if (accessor.Descriptor.IsMap)
336 {
337 IDictionary dictionary = (IDictionary) value;
338 return dictionary.Count == 0;
339 }
340 if (accessor.Descriptor.IsRepeated)
341 {
342 IList list = (IList) value;
343 return list.Count == 0;
344 }
345 switch (accessor.Descriptor.FieldType)
346 {
347 case FieldType.Bool:
348 return (bool) value == false;
349 case FieldType.Bytes:
350 return (ByteString) value == ByteString.Empty;
351 case FieldType.String:
352 return (string) value == "";
353 case FieldType.Double:
354 return (double) value == 0.0;
355 case FieldType.SInt32:
356 case FieldType.Int32:
357 case FieldType.SFixed32:
358 case FieldType.Enum:
359 return (int) value == 0;
360 case FieldType.Fixed32:
361 case FieldType.UInt32:
362 return (uint) value == 0;
363 case FieldType.Fixed64:
364 case FieldType.UInt64:
365 return (ulong) value == 0;
366 case FieldType.SFixed64:
367 case FieldType.Int64:
368 case FieldType.SInt64:
369 return (long) value == 0;
370 case FieldType.Float:
371 return (float) value == 0f;
372 case FieldType.Message:
373 case FieldType.Group: // Never expect to get this, but...
374 return value == null;
375 default:
376 throw new ArgumentException("Invalid field type");
377 }
378 }
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100379
avgwebad2d7752016-03-06 17:50:02 -0800380 private void WriteValue(TextWriter writer, object value)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100381 {
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100382 if (value == null)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100383 {
avgwebad2d7752016-03-06 17:50:02 -0800384 WriteNull(writer);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100385 }
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100386 else if (value is bool)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100387 {
avgwebad2d7752016-03-06 17:50:02 -0800388 writer.Write((bool)value ? "true" : "false");
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100389 }
390 else if (value is ByteString)
391 {
392 // Nothing in Base64 needs escaping
avgwebad2d7752016-03-06 17:50:02 -0800393 writer.Write('"');
394 writer.Write(((ByteString)value).ToBase64());
395 writer.Write('"');
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100396 }
397 else if (value is string)
398 {
avgwebad2d7752016-03-06 17:50:02 -0800399 WriteString(writer, (string)value);
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100400 }
401 else if (value is IDictionary)
402 {
avgwebad2d7752016-03-06 17:50:02 -0800403 WriteDictionary(writer, (IDictionary)value);
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100404 }
405 else if (value is IList)
406 {
avgwebad2d7752016-03-06 17:50:02 -0800407 WriteList(writer, (IList)value);
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100408 }
409 else if (value is int || value is uint)
410 {
411 IFormattable formattable = (IFormattable) value;
avgwebad2d7752016-03-06 17:50:02 -0800412 writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100413 }
414 else if (value is long || value is ulong)
415 {
avgwebad2d7752016-03-06 17:50:02 -0800416 writer.Write('"');
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100417 IFormattable formattable = (IFormattable) value;
avgwebad2d7752016-03-06 17:50:02 -0800418 writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
419 writer.Write('"');
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100420 }
421 else if (value is System.Enum)
422 {
Jon Skeet52db5132016-01-15 13:45:53 +0000423 if (System.Enum.IsDefined(value.GetType(), value))
424 {
avgwebad2d7752016-03-06 17:50:02 -0800425 WriteString(writer, value.ToString());
Jon Skeet52db5132016-01-15 13:45:53 +0000426 }
427 else
428 {
avgwebad2d7752016-03-06 17:50:02 -0800429 WriteValue(writer, (int)value);
Jon Skeet52db5132016-01-15 13:45:53 +0000430 }
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100431 }
432 else if (value is float || value is double)
433 {
434 string text = ((IFormattable) value).ToString("r", CultureInfo.InvariantCulture);
435 if (text == "NaN" || text == "Infinity" || text == "-Infinity")
436 {
avgwebad2d7752016-03-06 17:50:02 -0800437 writer.Write('"');
438 writer.Write(text);
439 writer.Write('"');
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100440 }
441 else
442 {
avgwebad2d7752016-03-06 17:50:02 -0800443 writer.Write(text);
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100444 }
445 }
446 else if (value is IMessage)
447 {
448 IMessage message = (IMessage) value;
449 if (message.Descriptor.IsWellKnownType)
450 {
avgwebad2d7752016-03-06 17:50:02 -0800451 WriteWellKnownTypeValue(writer, message.Descriptor, value);
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100452 }
453 else
454 {
avgwebad2d7752016-03-06 17:50:02 -0800455 WriteMessage(writer, (IMessage)value);
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100456 }
Jon Skeetf8c151f2015-07-03 11:56:29 +0100457 }
458 else
459 {
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100460 throw new ArgumentException("Unable to format value of type " + value.GetType());
Jon Skeetf8c151f2015-07-03 11:56:29 +0100461 }
462 }
463
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100464 /// <summary>
465 /// Central interception point for well-known type formatting. Any well-known types which
Jon Skeet16e272e2015-07-31 13:22:15 +0100466 /// don't need special handling can fall back to WriteMessage. We avoid assuming that the
467 /// values are using the embedded well-known types, in order to allow for dynamic messages
468 /// in the future.
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100469 /// </summary>
avgwebad2d7752016-03-06 17:50:02 -0800470 private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value)
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100471 {
Jon Skeet567579b2015-11-23 12:43:54 +0000472 // Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*,
473 // this would do the right thing.
Jon Skeete7caf152015-07-31 15:07:50 +0100474 if (value == null)
475 {
avgwebad2d7752016-03-06 17:50:02 -0800476 WriteNull(writer);
Jon Skeete7caf152015-07-31 15:07:50 +0100477 return;
478 }
Jon Skeetfb248822015-09-04 12:41:14 +0100479 // For wrapper types, the value will either be the (possibly boxed) "native" value,
480 // or the message itself if we're formatting it at the top level (e.g. just calling ToString on the object itself).
481 // If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value,
482 // and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string...
483 // WriteValue will do the right thing.)
Jon Skeet72ec3362015-11-19 17:13:38 +0000484 if (descriptor.IsWrapperType)
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100485 {
Jon Skeetfb248822015-09-04 12:41:14 +0100486 if (value is IMessage)
487 {
488 var message = (IMessage) value;
Jon Skeet284bb452015-11-05 09:13:53 +0000489 value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message);
Jon Skeetfb248822015-09-04 12:41:14 +0100490 }
avgwebad2d7752016-03-06 17:50:02 -0800491 WriteValue(writer, value);
Jon Skeet16e272e2015-07-31 13:22:15 +0100492 return;
493 }
Jon Skeete7caf152015-07-31 15:07:50 +0100494 if (descriptor.FullName == Timestamp.Descriptor.FullName)
Jon Skeet16e272e2015-07-31 13:22:15 +0100495 {
avgwebad2d7752016-03-06 17:50:02 -0800496 WriteTimestamp(writer, (IMessage)value);
Jon Skeet16e272e2015-07-31 13:22:15 +0100497 return;
498 }
Jon Skeete7caf152015-07-31 15:07:50 +0100499 if (descriptor.FullName == Duration.Descriptor.FullName)
Jon Skeet16e272e2015-07-31 13:22:15 +0100500 {
avgwebad2d7752016-03-06 17:50:02 -0800501 WriteDuration(writer, (IMessage)value);
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100502 return;
503 }
Jon Skeet0e30de32015-08-03 11:43:07 +0100504 if (descriptor.FullName == FieldMask.Descriptor.FullName)
505 {
avgwebad2d7752016-03-06 17:50:02 -0800506 WriteFieldMask(writer, (IMessage)value);
Jon Skeet0e30de32015-08-03 11:43:07 +0100507 return;
508 }
Jon Skeete7caf152015-07-31 15:07:50 +0100509 if (descriptor.FullName == Struct.Descriptor.FullName)
510 {
avgwebad2d7752016-03-06 17:50:02 -0800511 WriteStruct(writer, (IMessage)value);
Jon Skeete7caf152015-07-31 15:07:50 +0100512 return;
513 }
514 if (descriptor.FullName == ListValue.Descriptor.FullName)
515 {
516 var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
avgwebad2d7752016-03-06 17:50:02 -0800517 WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value));
Jon Skeete7caf152015-07-31 15:07:50 +0100518 return;
519 }
520 if (descriptor.FullName == Value.Descriptor.FullName)
521 {
avgwebad2d7752016-03-06 17:50:02 -0800522 WriteStructFieldValue(writer, (IMessage)value);
Jon Skeete7caf152015-07-31 15:07:50 +0100523 return;
524 }
Jon Skeet567579b2015-11-23 12:43:54 +0000525 if (descriptor.FullName == Any.Descriptor.FullName)
526 {
avgwebad2d7752016-03-06 17:50:02 -0800527 WriteAny(writer, (IMessage)value);
Jon Skeet567579b2015-11-23 12:43:54 +0000528 return;
529 }
avgwebad2d7752016-03-06 17:50:02 -0800530 WriteMessage(writer, (IMessage)value);
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100531 }
532
avgwebad2d7752016-03-06 17:50:02 -0800533 private void WriteTimestamp(TextWriter writer, IMessage value)
Jon Skeet16e272e2015-07-31 13:22:15 +0100534 {
535 // TODO: In the common case where this *is* using the built-in Timestamp type, we could
536 // avoid all the reflection at this point, by casting to Timestamp. In the interests of
537 // avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
538 // it still works in that case.
539 int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value);
540 long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value);
avgwebad2d7752016-03-06 17:50:02 -0800541 writer.Write(Timestamp.ToJson(seconds, nanos, DiagnosticOnly));
Jon Skeet16e272e2015-07-31 13:22:15 +0100542 }
543
avgwebad2d7752016-03-06 17:50:02 -0800544 private void WriteDuration(TextWriter writer, IMessage value)
Jon Skeet16e272e2015-07-31 13:22:15 +0100545 {
Jon Skeet801b1692015-08-03 08:45:48 +0100546 // TODO: Same as for WriteTimestamp
Jon Skeet16e272e2015-07-31 13:22:15 +0100547 int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
548 long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
avgwebad2d7752016-03-06 17:50:02 -0800549 writer.Write(Duration.ToJson(seconds, nanos, DiagnosticOnly));
Jon Skeet801b1692015-08-03 08:45:48 +0100550 }
551
avgwebad2d7752016-03-06 17:50:02 -0800552 private void WriteFieldMask(TextWriter writer, IMessage value)
Jon Skeet0e30de32015-08-03 11:43:07 +0100553 {
Jon Skeetdd43dcc2016-01-20 18:43:00 +0000554 var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value);
avgwebad2d7752016-03-06 17:50:02 -0800555 writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
Jon Skeet0e30de32015-08-03 11:43:07 +0100556 }
557
avgwebad2d7752016-03-06 17:50:02 -0800558 private void WriteAny(TextWriter writer, IMessage value)
Jon Skeet567579b2015-11-23 12:43:54 +0000559 {
Jon Skeetdd43dcc2016-01-20 18:43:00 +0000560 if (DiagnosticOnly)
Jon Skeetaabc6c42015-12-15 09:23:38 +0000561 {
avgwebad2d7752016-03-06 17:50:02 -0800562 WriteDiagnosticOnlyAny(writer, value);
Jon Skeetaabc6c42015-12-15 09:23:38 +0000563 return;
564 }
565
Jon Skeet567579b2015-11-23 12:43:54 +0000566 string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
567 ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
568 string typeName = GetTypeName(typeUrl);
569 MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
570 if (descriptor == null)
571 {
572 throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
573 }
574 IMessage message = descriptor.Parser.ParseFrom(data);
avgwebad2d7752016-03-06 17:50:02 -0800575 writer.Write("{ ");
576 WriteString(writer, AnyTypeUrlField);
577 writer.Write(NameValueSeparator);
578 WriteString(writer, typeUrl);
Jon Skeet567579b2015-11-23 12:43:54 +0000579
580 if (descriptor.IsWellKnownType)
581 {
avgwebad2d7752016-03-06 17:50:02 -0800582 writer.Write(PropertySeparator);
583 WriteString(writer, AnyWellKnownTypeValueField);
584 writer.Write(NameValueSeparator);
585 WriteWellKnownTypeValue(writer, descriptor, message);
Jon Skeet567579b2015-11-23 12:43:54 +0000586 }
587 else
588 {
avgwebad2d7752016-03-06 17:50:02 -0800589 WriteMessageFields(writer, message, true);
Jon Skeet567579b2015-11-23 12:43:54 +0000590 }
avgwebad2d7752016-03-06 17:50:02 -0800591 writer.Write(" }");
Jon Skeet567579b2015-11-23 12:43:54 +0000592 }
593
avgwebad2d7752016-03-06 17:50:02 -0800594 private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
Jon Skeetaabc6c42015-12-15 09:23:38 +0000595 {
596 string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
597 ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
avgwebad2d7752016-03-06 17:50:02 -0800598 writer.Write("{ ");
599 WriteString(writer, AnyTypeUrlField);
600 writer.Write(NameValueSeparator);
601 WriteString(writer, typeUrl);
602 writer.Write(PropertySeparator);
603 WriteString(writer, AnyDiagnosticValueField);
604 writer.Write(NameValueSeparator);
605 writer.Write('"');
606 writer.Write(data.ToBase64());
607 writer.Write('"');
608 writer.Write(" }");
Jon Skeetaabc6c42015-12-15 09:23:38 +0000609 }
610
Jon Skeet567579b2015-11-23 12:43:54 +0000611 internal static string GetTypeName(String typeUrl)
612 {
613 string[] parts = typeUrl.Split('/');
614 if (parts.Length != 2 || parts[0] != TypeUrlPrefix)
615 {
616 throw new InvalidProtocolBufferException($"Invalid type url: {typeUrl}");
617 }
618 return parts[1];
619 }
620
avgwebad2d7752016-03-06 17:50:02 -0800621 private void WriteStruct(TextWriter writer, IMessage message)
Jon Skeete7caf152015-07-31 15:07:50 +0100622 {
avgwebad2d7752016-03-06 17:50:02 -0800623 writer.Write("{ ");
Jon Skeete7caf152015-07-31 15:07:50 +0100624 IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message);
625 bool first = true;
626 foreach (DictionaryEntry entry in fields)
627 {
628 string key = (string) entry.Key;
629 IMessage value = (IMessage) entry.Value;
630 if (string.IsNullOrEmpty(key) || value == null)
631 {
632 throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
633 }
634
635 if (!first)
636 {
avgwebad2d7752016-03-06 17:50:02 -0800637 writer.Write(PropertySeparator);
Jon Skeete7caf152015-07-31 15:07:50 +0100638 }
avgwebad2d7752016-03-06 17:50:02 -0800639 WriteString(writer, key);
640 writer.Write(NameValueSeparator);
641 WriteStructFieldValue(writer, value);
Jon Skeete7caf152015-07-31 15:07:50 +0100642 first = false;
643 }
avgwebad2d7752016-03-06 17:50:02 -0800644 writer.Write(first ? "}" : " }");
Jon Skeete7caf152015-07-31 15:07:50 +0100645 }
646
avgwebad2d7752016-03-06 17:50:02 -0800647 private void WriteStructFieldValue(TextWriter writer, IMessage message)
Jon Skeete7caf152015-07-31 15:07:50 +0100648 {
649 var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message);
650 if (specifiedField == null)
651 {
652 throw new InvalidOperationException("Value message must contain a value for the oneof.");
653 }
654
655 object value = specifiedField.Accessor.GetValue(message);
656
657 switch (specifiedField.FieldNumber)
658 {
659 case Value.BoolValueFieldNumber:
660 case Value.StringValueFieldNumber:
661 case Value.NumberValueFieldNumber:
avgwebad2d7752016-03-06 17:50:02 -0800662 WriteValue(writer, value);
Jon Skeete7caf152015-07-31 15:07:50 +0100663 return;
664 case Value.StructValueFieldNumber:
665 case Value.ListValueFieldNumber:
666 // Structs and ListValues are nested messages, and already well-known types.
667 var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message);
avgwebad2d7752016-03-06 17:50:02 -0800668 WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage);
Jon Skeete7caf152015-07-31 15:07:50 +0100669 return;
670 case Value.NullValueFieldNumber:
avgwebad2d7752016-03-06 17:50:02 -0800671 WriteNull(writer);
Jon Skeete7caf152015-07-31 15:07:50 +0100672 return;
673 default:
674 throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber);
675 }
676 }
677
avgwebad2d7752016-03-06 17:50:02 -0800678 internal void WriteList(TextWriter writer, IList list)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100679 {
avgwebad2d7752016-03-06 17:50:02 -0800680 writer.Write("[ ");
Jon Skeetf8c151f2015-07-03 11:56:29 +0100681 bool first = true;
682 foreach (var value in list)
683 {
684 if (!first)
685 {
avgwebad2d7752016-03-06 17:50:02 -0800686 writer.Write(PropertySeparator);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100687 }
avgwebad2d7752016-03-06 17:50:02 -0800688 WriteValue(writer, value);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100689 first = false;
690 }
avgwebad2d7752016-03-06 17:50:02 -0800691 writer.Write(first ? "]" : " ]");
Jon Skeetf8c151f2015-07-03 11:56:29 +0100692 }
693
avgwebad2d7752016-03-06 17:50:02 -0800694 internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100695 {
avgwebad2d7752016-03-06 17:50:02 -0800696 writer.Write("{ ");
Jon Skeetf8c151f2015-07-03 11:56:29 +0100697 bool first = true;
Jon Skeetf8c151f2015-07-03 11:56:29 +0100698 // This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
699 foreach (DictionaryEntry pair in dictionary)
700 {
701 if (!first)
702 {
avgwebad2d7752016-03-06 17:50:02 -0800703 writer.Write(PropertySeparator);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100704 }
705 string keyText;
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100706 if (pair.Key is string)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100707 {
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100708 keyText = (string) pair.Key;
709 }
710 else if (pair.Key is bool)
711 {
712 keyText = (bool) pair.Key ? "true" : "false";
713 }
714 else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
715 {
716 keyText = ((IFormattable) pair.Key).ToString("d", CultureInfo.InvariantCulture);
717 }
718 else
719 {
720 if (pair.Key == null)
721 {
722 throw new ArgumentException("Dictionary has entry with null key");
723 }
724 throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
Jon Skeetf8c151f2015-07-03 11:56:29 +0100725 }
avgwebad2d7752016-03-06 17:50:02 -0800726 WriteString(writer, keyText);
727 writer.Write(NameValueSeparator);
728 WriteValue(writer, pair.Value);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100729 first = false;
730 }
avgwebad2d7752016-03-06 17:50:02 -0800731 writer.Write(first ? "}" : " }");
Jon Skeetf8c151f2015-07-03 11:56:29 +0100732 }
733
734 /// <summary>
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100735 /// Returns whether or not a singular value can be represented in JSON.
736 /// Currently only relevant for enums, where unknown values can't be represented.
737 /// For repeated/map fields, this always returns true.
738 /// </summary>
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100739 private bool CanWriteSingleValue(object value)
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100740 {
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100741 if (value is System.Enum)
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100742 {
Jon Skeet9ed6d4d2015-09-28 17:28:02 +0100743 return System.Enum.IsDefined(value.GetType(), value);
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100744 }
745 return true;
746 }
747
748 /// <summary>
Jon Skeetf8c151f2015-07-03 11:56:29 +0100749 /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
750 /// </summary>
751 /// <remarks>
752 /// Other than surrogate pair handling, this code is mostly taken from src/google/protobuf/util/internal/json_escaping.cc.
753 /// </remarks>
avgwebad2d7752016-03-06 17:50:02 -0800754 internal static void WriteString(TextWriter writer, string text)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100755 {
avgwebad2d7752016-03-06 17:50:02 -0800756 writer.Write('"');
Jon Skeetf8c151f2015-07-03 11:56:29 +0100757 for (int i = 0; i < text.Length; i++)
758 {
759 char c = text[i];
760 if (c < 0xa0)
761 {
avgwebad2d7752016-03-06 17:50:02 -0800762 writer.Write(CommonRepresentations[c]);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100763 continue;
764 }
765 if (char.IsHighSurrogate(c))
766 {
767 // Encountered first part of a surrogate pair.
768 // Check that we have the whole pair, and encode both parts as hex.
769 i++;
770 if (i == text.Length || !char.IsLowSurrogate(text[i]))
771 {
772 throw new ArgumentException("String contains low surrogate not followed by high surrogate");
773 }
avgwebad2d7752016-03-06 17:50:02 -0800774 HexEncodeUtf16CodeUnit(writer, c);
775 HexEncodeUtf16CodeUnit(writer, text[i]);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100776 continue;
777 }
778 else if (char.IsLowSurrogate(c))
779 {
780 throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
781 }
782 switch ((uint) c)
783 {
784 // These are not required by json spec
785 // but used to prevent security bugs in javascript.
786 case 0xfeff: // Zero width no-break space
787 case 0xfff9: // Interlinear annotation anchor
788 case 0xfffa: // Interlinear annotation separator
789 case 0xfffb: // Interlinear annotation terminator
790
791 case 0x00ad: // Soft-hyphen
792 case 0x06dd: // Arabic end of ayah
793 case 0x070f: // Syriac abbreviation mark
794 case 0x17b4: // Khmer vowel inherent Aq
795 case 0x17b5: // Khmer vowel inherent Aa
avgwebad2d7752016-03-06 17:50:02 -0800796 HexEncodeUtf16CodeUnit(writer, c);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100797 break;
798
799 default:
800 if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs
801 (c >= 0x200b && c <= 0x200f) || // Zero width etc.
802 (c >= 0x2028 && c <= 0x202e) || // Separators etc.
803 (c >= 0x2060 && c <= 0x2064) || // Invisible etc.
804 (c >= 0x206a && c <= 0x206f))
805 {
avgwebad2d7752016-03-06 17:50:02 -0800806 HexEncodeUtf16CodeUnit(writer, c);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100807 }
808 else
809 {
810 // No handling of surrogates here - that's done earlier
avgwebad2d7752016-03-06 17:50:02 -0800811 writer.Write(c);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100812 }
813 break;
814 }
815 }
avgwebad2d7752016-03-06 17:50:02 -0800816 writer.Write('"');
Jon Skeet801b1692015-08-03 08:45:48 +0100817 }
Jon Skeetf8c151f2015-07-03 11:56:29 +0100818
819 private const string Hex = "0123456789abcdef";
avgwebad2d7752016-03-06 17:50:02 -0800820 private static void HexEncodeUtf16CodeUnit(TextWriter writer, char c)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100821 {
avgwebad2d7752016-03-06 17:50:02 -0800822 writer.Write("\\u");
823 writer.Write(Hex[(c >> 12) & 0xf]);
824 writer.Write(Hex[(c >> 8) & 0xf]);
825 writer.Write(Hex[(c >> 4) & 0xf]);
826 writer.Write(Hex[(c >> 0) & 0xf]);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100827 }
828
829 /// <summary>
830 /// Settings controlling JSON formatting.
831 /// </summary>
832 public sealed class Settings
833 {
Jon Skeetf8c151f2015-07-03 11:56:29 +0100834 /// <summary>
835 /// Default settings, as used by <see cref="JsonFormatter.Default"/>
836 /// </summary>
Jon Skeet3de2fce2015-11-23 16:21:47 +0000837 public static Settings Default { get; }
838
839 // Workaround for the Mono compiler complaining about XML comments not being on
840 // valid language elements.
841 static Settings()
842 {
843 Default = new Settings(false);
844 }
Jon Skeetf8c151f2015-07-03 11:56:29 +0100845
Jon Skeetf8c151f2015-07-03 11:56:29 +0100846 /// <summary>
847 /// Whether fields whose values are the default for the field type (e.g. 0 for integers)
848 /// should be formatted (true) or omitted (false).
849 /// </summary>
Jon Skeet567579b2015-11-23 12:43:54 +0000850 public bool FormatDefaultValues { get; }
Jon Skeetf8c151f2015-07-03 11:56:29 +0100851
Jon Skeet811fc892015-08-04 15:58:39 +0100852 /// <summary>
Jon Skeet567579b2015-11-23 12:43:54 +0000853 /// The type registry used to format <see cref="Any"/> messages.
854 /// </summary>
855 public TypeRegistry TypeRegistry { get; }
856
857 // TODO: Work out how we're going to scale this to multiple settings. "WithXyz" methods?
858
859 /// <summary>
860 /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
861 /// and an empty type registry.
Jon Skeet811fc892015-08-04 15:58:39 +0100862 /// </summary>
863 /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
Jon Skeet567579b2015-11-23 12:43:54 +0000864 public Settings(bool formatDefaultValues) : this(formatDefaultValues, TypeRegistry.Empty)
Jon Skeetf8c151f2015-07-03 11:56:29 +0100865 {
Jon Skeet567579b2015-11-23 12:43:54 +0000866 }
867
868 /// <summary>
869 /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
870 /// and type registry.
871 /// </summary>
872 /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
873 /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
874 public Settings(bool formatDefaultValues, TypeRegistry typeRegistry)
875 {
876 FormatDefaultValues = formatDefaultValues;
Jon Skeet7762f162016-02-04 06:51:54 +0000877 TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry));
Jon Skeetf8c151f2015-07-03 11:56:29 +0100878 }
879 }
880 }
881}