Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Side by Side Diff: third_party/protobuf/csharp/src/Google.Protobuf/JsonFormatter.cs

Issue 1983203003: Update third_party/protobuf to protobuf-v3.0.0-beta-3 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: owners Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #region Copyright notice and license 1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format 2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2015 Google Inc. All rights reserved. 3 // Copyright 2015 Google Inc. All rights reserved.
4 // https://developers.google.com/protocol-buffers/ 4 // https://developers.google.com/protocol-buffers/
5 // 5 //
6 // Redistribution and use in source and binary forms, with or without 6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are 7 // modification, are permitted provided that the following conditions are
8 // met: 8 // met:
9 // 9 //
10 // * Redistributions of source code must retain the above copyright 10 // * Redistributions of source code must retain the above copyright
(...skipping 18 matching lines...) Expand all
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 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. 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion 31 #endregion
32 32
33 using System; 33 using System;
34 using System.Collections; 34 using System.Collections;
35 using System.Globalization; 35 using System.Globalization;
36 using System.Text; 36 using System.Text;
37 using Google.Protobuf.Reflection; 37 using Google.Protobuf.Reflection;
38 using Google.Protobuf.WellKnownTypes; 38 using Google.Protobuf.WellKnownTypes;
39 using System.IO;
39 using System.Linq; 40 using System.Linq;
41 using System.Collections.Generic;
42 using System.Reflection;
40 43
41 namespace Google.Protobuf 44 namespace Google.Protobuf
42 { 45 {
43 /// <summary> 46 /// <summary>
44 /// Reflection-based converter from messages to JSON. 47 /// Reflection-based converter from messages to JSON.
45 /// </summary> 48 /// </summary>
46 /// <remarks> 49 /// <remarks>
47 /// <para> 50 /// <para>
48 /// Instances of this class are thread-safe, with no mutable state. 51 /// Instances of this class are thread-safe, with no mutable state.
49 /// </para> 52 /// </para>
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
115 { 118 {
116 if (CommonRepresentations[i] == "") 119 if (CommonRepresentations[i] == "")
117 { 120 {
118 CommonRepresentations[i] = ((char) i).ToString(); 121 CommonRepresentations[i] = ((char) i).ToString();
119 } 122 }
120 } 123 }
121 } 124 }
122 125
123 private readonly Settings settings; 126 private readonly Settings settings;
124 127
128 private bool DiagnosticOnly => ReferenceEquals(this, diagnosticFormatter );
129
125 /// <summary> 130 /// <summary>
126 /// Creates a new formatted with the given settings. 131 /// Creates a new formatted with the given settings.
127 /// </summary> 132 /// </summary>
128 /// <param name="settings">The settings.</param> 133 /// <param name="settings">The settings.</param>
129 public JsonFormatter(Settings settings) 134 public JsonFormatter(Settings settings)
130 { 135 {
131 this.settings = settings; 136 this.settings = settings;
132 } 137 }
133 138
134 /// <summary> 139 /// <summary>
135 /// Formats the specified message as JSON. 140 /// Formats the specified message as JSON.
136 /// </summary> 141 /// </summary>
137 /// <param name="message">The message to format.</param> 142 /// <param name="message">The message to format.</param>
138 /// <returns>The formatted message.</returns> 143 /// <returns>The formatted message.</returns>
139 public string Format(IMessage message) 144 public string Format(IMessage message)
140 { 145 {
141 Preconditions.CheckNotNull(message, nameof(message)); 146 var writer = new StringWriter();
142 StringBuilder builder = new StringBuilder(); 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 t o.</param>
156 /// <returns>The formatted message.</returns>
157 public void Format(IMessage message, TextWriter writer)
158 {
159 ProtoPreconditions.CheckNotNull(message, nameof(message));
160 ProtoPreconditions.CheckNotNull(writer, nameof(writer));
161
143 if (message.Descriptor.IsWellKnownType) 162 if (message.Descriptor.IsWellKnownType)
144 { 163 {
145 WriteWellKnownTypeValue(builder, message.Descriptor, message, fa lse); 164 WriteWellKnownTypeValue(writer, message.Descriptor, message);
146 } 165 }
147 else 166 else
148 { 167 {
149 WriteMessage(builder, message); 168 WriteMessage(writer, message);
150 } 169 }
151 return builder.ToString();
152 } 170 }
153 171
154 /// <summary> 172 /// <summary>
155 /// Converts a message to JSON for diagnostic purposes with no extra con text. 173 /// Converts a message to JSON for diagnostic purposes with no extra con text.
156 /// </summary> 174 /// </summary>
157 /// <remarks> 175 /// <remarks>
158 /// <para> 176 /// <para>
159 /// This differs from calling <see cref="Format(IMessage)"/> on the defa ult JSON 177 /// This differs from calling <see cref="Format(IMessage)"/> on the defa ult JSON
160 /// formatter in its handling of <see cref="Any"/>. As no type registry is available 178 /// formatter in its handling of <see cref="Any"/>. As no type registry is available
161 /// in <see cref="object.ToString"/> calls, the normal way of resolving the type of 179 /// in <see cref="object.ToString"/> calls, the normal way of resolving the type of
162 /// an <c>Any</c> message cannot be applied. Instead, a JSON property na med <c>@value</c> 180 /// an <c>Any</c> message cannot be applied. Instead, a JSON property na med <c>@value</c>
163 /// is included with the base64 data from the <see cref="Any.Value"/> pr operty of the message. 181 /// is included with the base64 data from the <see cref="Any.Value"/> pr operty of the message.
164 /// </para> 182 /// </para>
165 /// <para>The value returned by this method is only designed to be used for diagnostic 183 /// <para>The value returned by this method is only designed to be used for diagnostic
166 /// purposes. It may not be parsable by <see cref="JsonParser"/>, and ma y not be parsable 184 /// purposes. It may not be parsable by <see cref="JsonParser"/>, and ma y not be parsable
167 /// by other Protocol Buffer implementations.</para> 185 /// by other Protocol Buffer implementations.</para>
168 /// </remarks> 186 /// </remarks>
169 /// <param name="message">The message to format for diagnostic purposes. </param> 187 /// <param name="message">The message to format for diagnostic purposes. </param>
170 /// <returns>The diagnostic-only JSON representation of the message</ret urns> 188 /// <returns>The diagnostic-only JSON representation of the message</ret urns>
171 public static string ToDiagnosticString(IMessage message) 189 public static string ToDiagnosticString(IMessage message)
172 { 190 {
173 Preconditions.CheckNotNull(message, nameof(message)); 191 ProtoPreconditions.CheckNotNull(message, nameof(message));
174 return diagnosticFormatter.Format(message); 192 return diagnosticFormatter.Format(message);
175 } 193 }
176 194
177 private void WriteMessage(StringBuilder builder, IMessage message) 195 private void WriteMessage(TextWriter writer, IMessage message)
178 { 196 {
179 if (message == null) 197 if (message == null)
180 { 198 {
181 WriteNull(builder); 199 WriteNull(writer);
182 return; 200 return;
183 } 201 }
184 builder.Append("{ "); 202 if (DiagnosticOnly)
185 bool writtenFields = WriteMessageFields(builder, message, false); 203 {
186 builder.Append(writtenFields ? " }" : "}"); 204 ICustomDiagnosticMessage customDiagnosticMessage = message as IC ustomDiagnosticMessage;
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 ? " }" : "}");
187 } 214 }
188 215
189 private bool WriteMessageFields(StringBuilder builder, IMessage message, bool assumeFirstFieldWritten) 216 private bool WriteMessageFields(TextWriter writer, IMessage message, boo l assumeFirstFieldWritten)
190 { 217 {
191 var fields = message.Descriptor.Fields; 218 var fields = message.Descriptor.Fields;
192 bool first = !assumeFirstFieldWritten; 219 bool first = !assumeFirstFieldWritten;
193 // First non-oneof fields 220 // First non-oneof fields
194 foreach (var field in fields.InFieldNumberOrder()) 221 foreach (var field in fields.InFieldNumberOrder())
195 { 222 {
196 var accessor = field.Accessor; 223 var accessor = field.Accessor;
197 if (field.ContainingOneof != null && field.ContainingOneof.Acces sor.GetCaseFieldDescriptor(message) != field) 224 if (field.ContainingOneof != null && field.ContainingOneof.Acces sor.GetCaseFieldDescriptor(message) != field)
198 { 225 {
199 continue; 226 continue;
200 } 227 }
201 // Omit default values unless we're asked to format them, or the y're oneofs (where the default 228 // Omit default values unless we're asked to format them, or the y're oneofs (where the default
202 // value is still formatted regardless, because that's how we pr eserve the oneof case). 229 // value is still formatted regardless, because that's how we pr eserve the oneof case).
203 object value = accessor.GetValue(message); 230 object value = accessor.GetValue(message);
204 if (field.ContainingOneof == null && !settings.FormatDefaultValu es && IsDefaultValue(accessor, value)) 231 if (field.ContainingOneof == null && !settings.FormatDefaultValu es && IsDefaultValue(accessor, value))
205 { 232 {
206 continue; 233 continue;
207 } 234 }
208 // Omit awkward (single) values such as unknown enum values
209 if (!field.IsRepeated && !field.IsMap && !CanWriteSingleValue(va lue))
210 {
211 continue;
212 }
213 235
214 // Okay, all tests complete: let's write the field value... 236 // Okay, all tests complete: let's write the field value...
215 if (!first) 237 if (!first)
216 { 238 {
217 builder.Append(PropertySeparator); 239 writer.Write(PropertySeparator);
218 } 240 }
219 WriteString(builder, ToCamelCase(accessor.Descriptor.Name)); 241
220 builder.Append(NameValueSeparator); 242 WriteString(writer, accessor.Descriptor.JsonName);
221 WriteValue(builder, value); 243 writer.Write(NameValueSeparator);
244 WriteValue(writer, value);
245
222 first = false; 246 first = false;
223 } 247 }
224 return !first; 248 return !first;
225 } 249 }
226 250
251 /// <summary>
252 /// Camel-case converter with added strictness for field mask formatting .
253 /// </summary>
254 /// <exception cref="InvalidOperationException">The field mask is invali d for JSON representation</exception>
255 private static string ToCamelCaseForFieldMask(string input)
256 {
257 for (int i = 0; i < input.Length; i++)
258 {
259 char c = input[i];
260 if (c >= 'A' && c <= 'Z')
261 {
262 throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
263 }
264 if (c == '_' && i < input.Length - 1)
265 {
266 char next = input[i + 1];
267 if (next < 'a' || next > 'z')
268 {
269 throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
270 }
271 }
272 }
273 return ToCamelCase(input);
274 }
275
227 // Converted from src/google/protobuf/util/internal/utility.cc ToCamelCa se 276 // Converted from src/google/protobuf/util/internal/utility.cc ToCamelCa se
228 // TODO: Use the new field in FieldDescriptor. 277 // TODO: Use the new field in FieldDescriptor.
229 internal static string ToCamelCase(string input) 278 internal static string ToCamelCase(string input)
230 { 279 {
231 bool capitalizeNext = false; 280 bool capitalizeNext = false;
232 bool wasCap = true; 281 bool wasCap = true;
233 bool isCap = false; 282 bool isCap = false;
234 bool firstWord = true; 283 bool firstWord = true;
235 StringBuilder result = new StringBuilder(input.Length); 284 StringBuilder result = new StringBuilder(input.Length);
236 285
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 { 319 {
271 result.Append(char.ToUpperInvariant(input[i])); 320 result.Append(char.ToUpperInvariant(input[i]));
272 continue; 321 continue;
273 } 322 }
274 } 323 }
275 result.Append(input[i]); 324 result.Append(input[i]);
276 } 325 }
277 return result.ToString(); 326 return result.ToString();
278 } 327 }
279 328
280 private static void WriteNull(StringBuilder builder) 329 private static void WriteNull(TextWriter writer)
281 { 330 {
282 builder.Append("null"); 331 writer.Write("null");
283 } 332 }
284 333
285 private static bool IsDefaultValue(IFieldAccessor accessor, object value ) 334 private static bool IsDefaultValue(IFieldAccessor accessor, object value )
286 { 335 {
287 if (accessor.Descriptor.IsMap) 336 if (accessor.Descriptor.IsMap)
288 { 337 {
289 IDictionary dictionary = (IDictionary) value; 338 IDictionary dictionary = (IDictionary) value;
290 return dictionary.Count == 0; 339 return dictionary.Count == 0;
291 } 340 }
292 if (accessor.Descriptor.IsRepeated) 341 if (accessor.Descriptor.IsRepeated)
(...skipping 29 matching lines...) Expand all
322 case FieldType.Float: 371 case FieldType.Float:
323 return (float) value == 0f; 372 return (float) value == 0f;
324 case FieldType.Message: 373 case FieldType.Message:
325 case FieldType.Group: // Never expect to get this, but... 374 case FieldType.Group: // Never expect to get this, but...
326 return value == null; 375 return value == null;
327 default: 376 default:
328 throw new ArgumentException("Invalid field type"); 377 throw new ArgumentException("Invalid field type");
329 } 378 }
330 } 379 }
331 380
332 private void WriteValue(StringBuilder builder, object value) 381 private void WriteValue(TextWriter writer, object value)
333 { 382 {
334 if (value == null) 383 if (value == null)
335 { 384 {
336 WriteNull(builder); 385 WriteNull(writer);
337 } 386 }
338 else if (value is bool) 387 else if (value is bool)
339 { 388 {
340 builder.Append((bool) value ? "true" : "false"); 389 writer.Write((bool)value ? "true" : "false");
341 } 390 }
342 else if (value is ByteString) 391 else if (value is ByteString)
343 { 392 {
344 // Nothing in Base64 needs escaping 393 // Nothing in Base64 needs escaping
345 builder.Append('"'); 394 writer.Write('"');
346 builder.Append(((ByteString) value).ToBase64()); 395 writer.Write(((ByteString)value).ToBase64());
347 builder.Append('"'); 396 writer.Write('"');
348 } 397 }
349 else if (value is string) 398 else if (value is string)
350 { 399 {
351 WriteString(builder, (string) value); 400 WriteString(writer, (string)value);
352 } 401 }
353 else if (value is IDictionary) 402 else if (value is IDictionary)
354 { 403 {
355 WriteDictionary(builder, (IDictionary) value); 404 WriteDictionary(writer, (IDictionary)value);
356 } 405 }
357 else if (value is IList) 406 else if (value is IList)
358 { 407 {
359 WriteList(builder, (IList) value); 408 WriteList(writer, (IList)value);
360 } 409 }
361 else if (value is int || value is uint) 410 else if (value is int || value is uint)
362 { 411 {
363 IFormattable formattable = (IFormattable) value; 412 IFormattable formattable = (IFormattable) value;
364 builder.Append(formattable.ToString("d", CultureInfo.InvariantCu lture)); 413 writer.Write(formattable.ToString("d", CultureInfo.InvariantCult ure));
365 } 414 }
366 else if (value is long || value is ulong) 415 else if (value is long || value is ulong)
367 { 416 {
368 builder.Append('"'); 417 writer.Write('"');
369 IFormattable formattable = (IFormattable) value; 418 IFormattable formattable = (IFormattable) value;
370 builder.Append(formattable.ToString("d", CultureInfo.InvariantCu lture)); 419 writer.Write(formattable.ToString("d", CultureInfo.InvariantCult ure));
371 builder.Append('"'); 420 writer.Write('"');
372 } 421 }
373 else if (value is System.Enum) 422 else if (value is System.Enum)
374 { 423 {
375 WriteString(builder, value.ToString()); 424 string name = OriginalEnumValueHelper.GetOriginalName(value);
425 if (name != null)
426 {
427 WriteString(writer, name);
428 }
429 else
430 {
431 WriteValue(writer, (int)value);
432 }
376 } 433 }
377 else if (value is float || value is double) 434 else if (value is float || value is double)
378 { 435 {
379 string text = ((IFormattable) value).ToString("r", CultureInfo.I nvariantCulture); 436 string text = ((IFormattable) value).ToString("r", CultureInfo.I nvariantCulture);
380 if (text == "NaN" || text == "Infinity" || text == "-Infinity") 437 if (text == "NaN" || text == "Infinity" || text == "-Infinity")
381 { 438 {
382 builder.Append('"'); 439 writer.Write('"');
383 builder.Append(text); 440 writer.Write(text);
384 builder.Append('"'); 441 writer.Write('"');
385 } 442 }
386 else 443 else
387 { 444 {
388 builder.Append(text); 445 writer.Write(text);
389 } 446 }
390 } 447 }
391 else if (value is IMessage) 448 else if (value is IMessage)
392 { 449 {
393 IMessage message = (IMessage) value; 450 IMessage message = (IMessage) value;
394 if (message.Descriptor.IsWellKnownType) 451 if (message.Descriptor.IsWellKnownType)
395 { 452 {
396 WriteWellKnownTypeValue(builder, message.Descriptor, value, true); 453 WriteWellKnownTypeValue(writer, message.Descriptor, value);
397 } 454 }
398 else 455 else
399 { 456 {
400 WriteMessage(builder, (IMessage) value); 457 WriteMessage(writer, (IMessage)value);
401 } 458 }
402 } 459 }
403 else 460 else
404 { 461 {
405 throw new ArgumentException("Unable to format value of type " + value.GetType()); 462 throw new ArgumentException("Unable to format value of type " + value.GetType());
406 } 463 }
407 } 464 }
408 465
409 /// <summary> 466 /// <summary>
410 /// Central interception point for well-known type formatting. Any well- known types which 467 /// Central interception point for well-known type formatting. Any well- known types which
411 /// don't need special handling can fall back to WriteMessage. We avoid assuming that the 468 /// don't need special handling can fall back to WriteMessage. We avoid assuming that the
412 /// values are using the embedded well-known types, in order to allow fo r dynamic messages 469 /// values are using the embedded well-known types, in order to allow fo r dynamic messages
413 /// in the future. 470 /// in the future.
414 /// </summary> 471 /// </summary>
415 private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescr iptor descriptor, object value, bool inField) 472 private void WriteWellKnownTypeValue(TextWriter writer, MessageDescripto r descriptor, object value)
416 { 473 {
417 // Currently, we can never actually get here, because null values ar e always handled by the caller. But if we *could*, 474 // Currently, we can never actually get here, because null values ar e always handled by the caller. But if we *could*,
418 // this would do the right thing. 475 // this would do the right thing.
419 if (value == null) 476 if (value == null)
420 { 477 {
421 WriteNull(builder); 478 WriteNull(writer);
422 return; 479 return;
423 } 480 }
424 // For wrapper types, the value will either be the (possibly boxed) "native" value, 481 // For wrapper types, the value will either be the (possibly boxed) "native" value,
425 // or the message itself if we're formatting it at the top level (e. g. just calling ToString on the object itself). 482 // or the message itself if we're formatting it at the top level (e. g. just calling ToString on the object itself).
426 // If it's the message form, we can extract the value first, which * will* be the (possibly boxed) native value, 483 // If it's the message form, we can extract the value first, which * will* be the (possibly boxed) native value,
427 // and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string... 484 // and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string...
428 // WriteValue will do the right thing.) 485 // WriteValue will do the right thing.)
429 if (descriptor.IsWrapperType) 486 if (descriptor.IsWrapperType)
430 { 487 {
431 if (value is IMessage) 488 if (value is IMessage)
432 { 489 {
433 var message = (IMessage) value; 490 var message = (IMessage) value;
434 value = message.Descriptor.Fields[WrappersReflection.Wrapper ValueFieldNumber].Accessor.GetValue(message); 491 value = message.Descriptor.Fields[WrappersReflection.Wrapper ValueFieldNumber].Accessor.GetValue(message);
435 } 492 }
436 WriteValue(builder, value); 493 WriteValue(writer, value);
437 return; 494 return;
438 } 495 }
439 if (descriptor.FullName == Timestamp.Descriptor.FullName) 496 if (descriptor.FullName == Timestamp.Descriptor.FullName)
440 { 497 {
441 MaybeWrapInString(builder, value, WriteTimestamp, inField); 498 WriteTimestamp(writer, (IMessage)value);
442 return; 499 return;
443 } 500 }
444 if (descriptor.FullName == Duration.Descriptor.FullName) 501 if (descriptor.FullName == Duration.Descriptor.FullName)
445 { 502 {
446 MaybeWrapInString(builder, value, WriteDuration, inField); 503 WriteDuration(writer, (IMessage)value);
447 return; 504 return;
448 } 505 }
449 if (descriptor.FullName == FieldMask.Descriptor.FullName) 506 if (descriptor.FullName == FieldMask.Descriptor.FullName)
450 { 507 {
451 MaybeWrapInString(builder, value, WriteFieldMask, inField); 508 WriteFieldMask(writer, (IMessage)value);
452 return; 509 return;
453 } 510 }
454 if (descriptor.FullName == Struct.Descriptor.FullName) 511 if (descriptor.FullName == Struct.Descriptor.FullName)
455 { 512 {
456 WriteStruct(builder, (IMessage) value); 513 WriteStruct(writer, (IMessage)value);
457 return; 514 return;
458 } 515 }
459 if (descriptor.FullName == ListValue.Descriptor.FullName) 516 if (descriptor.FullName == ListValue.Descriptor.FullName)
460 { 517 {
461 var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumbe r].Accessor; 518 var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumbe r].Accessor;
462 WriteList(builder, (IList) fieldAccessor.GetValue((IMessage) val ue)); 519 WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value) );
463 return; 520 return;
464 } 521 }
465 if (descriptor.FullName == Value.Descriptor.FullName) 522 if (descriptor.FullName == Value.Descriptor.FullName)
466 { 523 {
467 WriteStructFieldValue(builder, (IMessage) value); 524 WriteStructFieldValue(writer, (IMessage)value);
468 return; 525 return;
469 } 526 }
470 if (descriptor.FullName == Any.Descriptor.FullName) 527 if (descriptor.FullName == Any.Descriptor.FullName)
471 { 528 {
472 WriteAny(builder, (IMessage) value); 529 WriteAny(writer, (IMessage)value);
473 return; 530 return;
474 } 531 }
475 WriteMessage(builder, (IMessage) value); 532 WriteMessage(writer, (IMessage)value);
476 } 533 }
477 534
478 /// <summary> 535 private void WriteTimestamp(TextWriter writer, IMessage value)
479 /// Some well-known types end up as string values... so they need wrappi ng in quotes, but only
480 /// when they're being used as fields within another message.
481 /// </summary>
482 private void MaybeWrapInString(StringBuilder builder, object value, Acti on<StringBuilder, IMessage> action, bool inField)
483 {
484 if (inField)
485 {
486 builder.Append('"');
487 action(builder, (IMessage) value);
488 builder.Append('"');
489 }
490 else
491 {
492 action(builder, (IMessage) value);
493 }
494 }
495
496 private void WriteTimestamp(StringBuilder builder, IMessage value)
497 { 536 {
498 // TODO: In the common case where this *is* using the built-in Times tamp type, we could 537 // TODO: In the common case where this *is* using the built-in Times tamp type, we could
499 // avoid all the reflection at this point, by casting to Timestamp. In the interests of 538 // avoid all the reflection at this point, by casting to Timestamp. In the interests of
500 // avoiding subtle bugs, don't do that until we've implemented Dynam icMessage so that we can prove 539 // avoiding subtle bugs, don't do that until we've implemented Dynam icMessage so that we can prove
501 // it still works in that case. 540 // it still works in that case.
502 int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber ].Accessor.GetValue(value); 541 int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber ].Accessor.GetValue(value);
503 long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsField Number].Accessor.GetValue(value); 542 long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsField Number].Accessor.GetValue(value);
504 543 writer.Write(Timestamp.ToJson(seconds, nanos, DiagnosticOnly));
505 // Even if the original message isn't using the built-in classes, we can still build one... and then
506 // rely on it being normalized.
507 Timestamp normalized = Timestamp.Normalize(seconds, nanos);
508 // Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
509 DateTime dateTime = normalized.ToDateTime();
510 builder.Append(dateTime.ToString("yyyy'-'MM'-'dd'T'HH:mm:ss", Cultur eInfo.InvariantCulture));
511 AppendNanoseconds(builder, Math.Abs(normalized.Nanos));
512 builder.Append('Z');
513 } 544 }
514 545
515 private void WriteDuration(StringBuilder builder, IMessage value) 546 private void WriteDuration(TextWriter writer, IMessage value)
516 { 547 {
517 // TODO: Same as for WriteTimestamp 548 // TODO: Same as for WriteTimestamp
518 int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber] .Accessor.GetValue(value); 549 int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber] .Accessor.GetValue(value);
519 long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldN umber].Accessor.GetValue(value); 550 long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldN umber].Accessor.GetValue(value);
520 551 writer.Write(Duration.ToJson(seconds, nanos, DiagnosticOnly));
521 // Even if the original message isn't using the built-in classes, we can still build one... and then
522 // rely on it being normalized.
523 Duration normalized = Duration.Normalize(seconds, nanos);
524
525 // The seconds part will normally provide the minus sign if we need it, but not if it's 0...
526 if (normalized.Seconds == 0 && normalized.Nanos < 0)
527 {
528 builder.Append('-');
529 }
530
531 builder.Append(normalized.Seconds.ToString("d", CultureInfo.Invarian tCulture));
532 AppendNanoseconds(builder, Math.Abs(normalized.Nanos));
533 builder.Append('s');
534 } 552 }
535 553
536 private void WriteFieldMask(StringBuilder builder, IMessage value) 554 private void WriteFieldMask(TextWriter writer, IMessage value)
537 { 555 {
538 IList paths = (IList) value.Descriptor.Fields[FieldMask.PathsFieldNu mber].Accessor.GetValue(value); 556 var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsF ieldNumber].Accessor.GetValue(value);
539 AppendEscapedString(builder, string.Join(",", paths.Cast<string>().S elect(ToCamelCase))); 557 writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
540 } 558 }
541 559
542 private void WriteAny(StringBuilder builder, IMessage value) 560 private void WriteAny(TextWriter writer, IMessage value)
543 { 561 {
544 if (ReferenceEquals(this, diagnosticFormatter)) 562 if (DiagnosticOnly)
545 { 563 {
546 WriteDiagnosticOnlyAny(builder, value); 564 WriteDiagnosticOnlyAny(writer, value);
547 return; 565 return;
548 } 566 }
549 567
550 string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNu mber].Accessor.GetValue(value); 568 string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNu mber].Accessor.GetValue(value);
551 ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFiel dNumber].Accessor.GetValue(value); 569 ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFiel dNumber].Accessor.GetValue(value);
552 string typeName = GetTypeName(typeUrl); 570 string typeName = Any.GetTypeName(typeUrl);
553 MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName); 571 MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
554 if (descriptor == null) 572 if (descriptor == null)
555 { 573 {
556 throw new InvalidOperationException($"Type registry has no descr iptor for type name '{typeName}'"); 574 throw new InvalidOperationException($"Type registry has no descr iptor for type name '{typeName}'");
557 } 575 }
558 IMessage message = descriptor.Parser.ParseFrom(data); 576 IMessage message = descriptor.Parser.ParseFrom(data);
559 builder.Append("{ "); 577 writer.Write("{ ");
560 WriteString(builder, AnyTypeUrlField); 578 WriteString(writer, AnyTypeUrlField);
561 builder.Append(NameValueSeparator); 579 writer.Write(NameValueSeparator);
562 WriteString(builder, typeUrl); 580 WriteString(writer, typeUrl);
563 581
564 if (descriptor.IsWellKnownType) 582 if (descriptor.IsWellKnownType)
565 { 583 {
566 builder.Append(PropertySeparator); 584 writer.Write(PropertySeparator);
567 WriteString(builder, AnyWellKnownTypeValueField); 585 WriteString(writer, AnyWellKnownTypeValueField);
568 builder.Append(NameValueSeparator); 586 writer.Write(NameValueSeparator);
569 WriteWellKnownTypeValue(builder, descriptor, message, true); 587 WriteWellKnownTypeValue(writer, descriptor, message);
570 } 588 }
571 else 589 else
572 { 590 {
573 WriteMessageFields(builder, message, true); 591 WriteMessageFields(writer, message, true);
574 } 592 }
575 builder.Append(" }"); 593 writer.Write(" }");
576 } 594 }
577 595
578 private void WriteDiagnosticOnlyAny(StringBuilder builder, IMessage valu e) 596 private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
579 { 597 {
580 string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNu mber].Accessor.GetValue(value); 598 string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNu mber].Accessor.GetValue(value);
581 ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFiel dNumber].Accessor.GetValue(value); 599 ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFiel dNumber].Accessor.GetValue(value);
582 builder.Append("{ "); 600 writer.Write("{ ");
583 WriteString(builder, AnyTypeUrlField); 601 WriteString(writer, AnyTypeUrlField);
584 builder.Append(NameValueSeparator); 602 writer.Write(NameValueSeparator);
585 WriteString(builder, typeUrl); 603 WriteString(writer, typeUrl);
586 builder.Append(PropertySeparator); 604 writer.Write(PropertySeparator);
587 WriteString(builder, AnyDiagnosticValueField); 605 WriteString(writer, AnyDiagnosticValueField);
588 builder.Append(NameValueSeparator); 606 writer.Write(NameValueSeparator);
589 builder.Append('"'); 607 writer.Write('"');
590 builder.Append(data.ToBase64()); 608 writer.Write(data.ToBase64());
591 builder.Append('"'); 609 writer.Write('"');
592 builder.Append(" }"); 610 writer.Write(" }");
593 } 611 }
594 612
595 internal static string GetTypeName(String typeUrl) 613 private void WriteStruct(TextWriter writer, IMessage message)
596 { 614 {
597 string[] parts = typeUrl.Split('/'); 615 writer.Write("{ ");
598 if (parts.Length != 2 || parts[0] != TypeUrlPrefix)
599 {
600 throw new InvalidProtocolBufferException($"Invalid type url: {ty peUrl}");
601 }
602 return parts[1];
603 }
604
605 /// <summary>
606 /// Appends a number of nanoseconds to a StringBuilder. Either 0 digits are added (in which
607 /// case no "." is appended), or 3 6 or 9 digits.
608 /// </summary>
609 private static void AppendNanoseconds(StringBuilder builder, int nanos)
610 {
611 if (nanos != 0)
612 {
613 builder.Append('.');
614 // Output to 3, 6 or 9 digits.
615 if (nanos % 1000000 == 0)
616 {
617 builder.Append((nanos / 1000000).ToString("d", CultureInfo.I nvariantCulture));
618 }
619 else if (nanos % 1000 == 0)
620 {
621 builder.Append((nanos / 1000).ToString("d", CultureInfo.Inva riantCulture));
622 }
623 else
624 {
625 builder.Append(nanos.ToString("d", CultureInfo.InvariantCult ure));
626 }
627 }
628 }
629
630 private void WriteStruct(StringBuilder builder, IMessage message)
631 {
632 builder.Append("{ ");
633 IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct. FieldsFieldNumber].Accessor.GetValue(message); 616 IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct. FieldsFieldNumber].Accessor.GetValue(message);
634 bool first = true; 617 bool first = true;
635 foreach (DictionaryEntry entry in fields) 618 foreach (DictionaryEntry entry in fields)
636 { 619 {
637 string key = (string) entry.Key; 620 string key = (string) entry.Key;
638 IMessage value = (IMessage) entry.Value; 621 IMessage value = (IMessage) entry.Value;
639 if (string.IsNullOrEmpty(key) || value == null) 622 if (string.IsNullOrEmpty(key) || value == null)
640 { 623 {
641 throw new InvalidOperationException("Struct fields cannot ha ve an empty key or a null value."); 624 throw new InvalidOperationException("Struct fields cannot ha ve an empty key or a null value.");
642 } 625 }
643 626
644 if (!first) 627 if (!first)
645 { 628 {
646 builder.Append(PropertySeparator); 629 writer.Write(PropertySeparator);
647 } 630 }
648 WriteString(builder, key); 631 WriteString(writer, key);
649 builder.Append(NameValueSeparator); 632 writer.Write(NameValueSeparator);
650 WriteStructFieldValue(builder, value); 633 WriteStructFieldValue(writer, value);
651 first = false; 634 first = false;
652 } 635 }
653 builder.Append(first ? "}" : " }"); 636 writer.Write(first ? "}" : " }");
654 } 637 }
655 638
656 private void WriteStructFieldValue(StringBuilder builder, IMessage messa ge) 639 private void WriteStructFieldValue(TextWriter writer, IMessage message)
657 { 640 {
658 var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFi eldDescriptor(message); 641 var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFi eldDescriptor(message);
659 if (specifiedField == null) 642 if (specifiedField == null)
660 { 643 {
661 throw new InvalidOperationException("Value message must contain a value for the oneof."); 644 throw new InvalidOperationException("Value message must contain a value for the oneof.");
662 } 645 }
663 646
664 object value = specifiedField.Accessor.GetValue(message); 647 object value = specifiedField.Accessor.GetValue(message);
665 648
666 switch (specifiedField.FieldNumber) 649 switch (specifiedField.FieldNumber)
667 { 650 {
668 case Value.BoolValueFieldNumber: 651 case Value.BoolValueFieldNumber:
669 case Value.StringValueFieldNumber: 652 case Value.StringValueFieldNumber:
670 case Value.NumberValueFieldNumber: 653 case Value.NumberValueFieldNumber:
671 WriteValue(builder, value); 654 WriteValue(writer, value);
672 return; 655 return;
673 case Value.StructValueFieldNumber: 656 case Value.StructValueFieldNumber:
674 case Value.ListValueFieldNumber: 657 case Value.ListValueFieldNumber:
675 // Structs and ListValues are nested messages, and already w ell-known types. 658 // Structs and ListValues are nested messages, and already w ell-known types.
676 var nestedMessage = (IMessage) specifiedField.Accessor.GetVa lue(message); 659 var nestedMessage = (IMessage) specifiedField.Accessor.GetVa lue(message);
677 WriteWellKnownTypeValue(builder, nestedMessage.Descriptor, n estedMessage, true); 660 WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, ne stedMessage);
678 return; 661 return;
679 case Value.NullValueFieldNumber: 662 case Value.NullValueFieldNumber:
680 WriteNull(builder); 663 WriteNull(writer);
681 return; 664 return;
682 default: 665 default:
683 throw new InvalidOperationException("Unexpected case in stru ct field: " + specifiedField.FieldNumber); 666 throw new InvalidOperationException("Unexpected case in stru ct field: " + specifiedField.FieldNumber);
684 } 667 }
685 } 668 }
686 669
687 internal void WriteList(StringBuilder builder, IList list) 670 internal void WriteList(TextWriter writer, IList list)
688 { 671 {
689 builder.Append("[ "); 672 writer.Write("[ ");
690 bool first = true; 673 bool first = true;
691 foreach (var value in list) 674 foreach (var value in list)
692 { 675 {
693 if (!CanWriteSingleValue(value))
694 {
695 continue;
696 }
697 if (!first) 676 if (!first)
698 { 677 {
699 builder.Append(PropertySeparator); 678 writer.Write(PropertySeparator);
700 } 679 }
701 WriteValue(builder, value); 680 WriteValue(writer, value);
702 first = false; 681 first = false;
703 } 682 }
704 builder.Append(first ? "]" : " ]"); 683 writer.Write(first ? "]" : " ]");
705 } 684 }
706 685
707 internal void WriteDictionary(StringBuilder builder, IDictionary diction ary) 686 internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
708 { 687 {
709 builder.Append("{ "); 688 writer.Write("{ ");
710 bool first = true; 689 bool first = true;
711 // This will box each pair. Could use IDictionaryEnumerator, but tha t's ugly in terms of disposal. 690 // This will box each pair. Could use IDictionaryEnumerator, but tha t's ugly in terms of disposal.
712 foreach (DictionaryEntry pair in dictionary) 691 foreach (DictionaryEntry pair in dictionary)
713 { 692 {
714 if (!CanWriteSingleValue(pair.Value))
715 {
716 continue;
717 }
718 if (!first) 693 if (!first)
719 { 694 {
720 builder.Append(PropertySeparator); 695 writer.Write(PropertySeparator);
721 } 696 }
722 string keyText; 697 string keyText;
723 if (pair.Key is string) 698 if (pair.Key is string)
724 { 699 {
725 keyText = (string) pair.Key; 700 keyText = (string) pair.Key;
726 } 701 }
727 else if (pair.Key is bool) 702 else if (pair.Key is bool)
728 { 703 {
729 keyText = (bool) pair.Key ? "true" : "false"; 704 keyText = (bool) pair.Key ? "true" : "false";
730 } 705 }
731 else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong) 706 else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
732 { 707 {
733 keyText = ((IFormattable) pair.Key).ToString("d", CultureInf o.InvariantCulture); 708 keyText = ((IFormattable) pair.Key).ToString("d", CultureInf o.InvariantCulture);
734 } 709 }
735 else 710 else
736 { 711 {
737 if (pair.Key == null) 712 if (pair.Key == null)
738 { 713 {
739 throw new ArgumentException("Dictionary has entry with n ull key"); 714 throw new ArgumentException("Dictionary has entry with n ull key");
740 } 715 }
741 throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType()); 716 throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
742 } 717 }
743 WriteString(builder, keyText); 718 WriteString(writer, keyText);
744 builder.Append(NameValueSeparator); 719 writer.Write(NameValueSeparator);
745 WriteValue(builder, pair.Value); 720 WriteValue(writer, pair.Value);
746 first = false; 721 first = false;
747 } 722 }
748 builder.Append(first ? "}" : " }"); 723 writer.Write(first ? "}" : " }");
749 } 724 }
750 725
751 /// <summary> 726 /// <summary>
752 /// Returns whether or not a singular value can be represented in JSON. 727 /// Returns whether or not a singular value can be represented in JSON.
753 /// Currently only relevant for enums, where unknown values can't be rep resented. 728 /// Currently only relevant for enums, where unknown values can't be rep resented.
754 /// For repeated/map fields, this always returns true. 729 /// For repeated/map fields, this always returns true.
755 /// </summary> 730 /// </summary>
756 private bool CanWriteSingleValue(object value) 731 private bool CanWriteSingleValue(object value)
757 { 732 {
758 if (value is System.Enum) 733 if (value is System.Enum)
759 { 734 {
760 return System.Enum.IsDefined(value.GetType(), value); 735 return System.Enum.IsDefined(value.GetType(), value);
761 } 736 }
762 return true; 737 return true;
763 } 738 }
764 739
765 /// <summary> 740 /// <summary>
766 /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required. 741 /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
767 /// </summary> 742 /// </summary>
768 /// <remarks> 743 /// <remarks>
769 /// Other than surrogate pair handling, this code is mostly taken from s rc/google/protobuf/util/internal/json_escaping.cc. 744 /// Other than surrogate pair handling, this code is mostly taken from s rc/google/protobuf/util/internal/json_escaping.cc.
770 /// </remarks> 745 /// </remarks>
771 private void WriteString(StringBuilder builder, string text) 746 internal static void WriteString(TextWriter writer, string text)
772 { 747 {
773 builder.Append('"'); 748 writer.Write('"');
774 AppendEscapedString(builder, text);
775 builder.Append('"');
776 }
777
778 /// <summary>
779 /// Appends the given text to the string builder, escaping as required.
780 /// </summary>
781 private void AppendEscapedString(StringBuilder builder, string text)
782 {
783 for (int i = 0; i < text.Length; i++) 749 for (int i = 0; i < text.Length; i++)
784 { 750 {
785 char c = text[i]; 751 char c = text[i];
786 if (c < 0xa0) 752 if (c < 0xa0)
787 { 753 {
788 builder.Append(CommonRepresentations[c]); 754 writer.Write(CommonRepresentations[c]);
789 continue; 755 continue;
790 } 756 }
791 if (char.IsHighSurrogate(c)) 757 if (char.IsHighSurrogate(c))
792 { 758 {
793 // Encountered first part of a surrogate pair. 759 // Encountered first part of a surrogate pair.
794 // Check that we have the whole pair, and encode both parts as hex. 760 // Check that we have the whole pair, and encode both parts as hex.
795 i++; 761 i++;
796 if (i == text.Length || !char.IsLowSurrogate(text[i])) 762 if (i == text.Length || !char.IsLowSurrogate(text[i]))
797 { 763 {
798 throw new ArgumentException("String contains low surroga te not followed by high surrogate"); 764 throw new ArgumentException("String contains low surroga te not followed by high surrogate");
799 } 765 }
800 HexEncodeUtf16CodeUnit(builder, c); 766 HexEncodeUtf16CodeUnit(writer, c);
801 HexEncodeUtf16CodeUnit(builder, text[i]); 767 HexEncodeUtf16CodeUnit(writer, text[i]);
802 continue; 768 continue;
803 } 769 }
804 else if (char.IsLowSurrogate(c)) 770 else if (char.IsLowSurrogate(c))
805 { 771 {
806 throw new ArgumentException("String contains high surrogate not preceded by low surrogate"); 772 throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
807 } 773 }
808 switch ((uint) c) 774 switch ((uint) c)
809 { 775 {
810 // These are not required by json spec 776 // These are not required by json spec
811 // but used to prevent security bugs in javascript. 777 // but used to prevent security bugs in javascript.
812 case 0xfeff: // Zero width no-break space 778 case 0xfeff: // Zero width no-break space
813 case 0xfff9: // Interlinear annotation anchor 779 case 0xfff9: // Interlinear annotation anchor
814 case 0xfffa: // Interlinear annotation separator 780 case 0xfffa: // Interlinear annotation separator
815 case 0xfffb: // Interlinear annotation terminator 781 case 0xfffb: // Interlinear annotation terminator
816 782
817 case 0x00ad: // Soft-hyphen 783 case 0x00ad: // Soft-hyphen
818 case 0x06dd: // Arabic end of ayah 784 case 0x06dd: // Arabic end of ayah
819 case 0x070f: // Syriac abbreviation mark 785 case 0x070f: // Syriac abbreviation mark
820 case 0x17b4: // Khmer vowel inherent Aq 786 case 0x17b4: // Khmer vowel inherent Aq
821 case 0x17b5: // Khmer vowel inherent Aa 787 case 0x17b5: // Khmer vowel inherent Aa
822 HexEncodeUtf16CodeUnit(builder, c); 788 HexEncodeUtf16CodeUnit(writer, c);
823 break; 789 break;
824 790
825 default: 791 default:
826 if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs 792 if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs
827 (c >= 0x200b && c <= 0x200f) || // Zero width etc. 793 (c >= 0x200b && c <= 0x200f) || // Zero width etc.
828 (c >= 0x2028 && c <= 0x202e) || // Separators etc. 794 (c >= 0x2028 && c <= 0x202e) || // Separators etc.
829 (c >= 0x2060 && c <= 0x2064) || // Invisible etc. 795 (c >= 0x2060 && c <= 0x2064) || // Invisible etc.
830 (c >= 0x206a && c <= 0x206f)) 796 (c >= 0x206a && c <= 0x206f))
831 { 797 {
832 HexEncodeUtf16CodeUnit(builder, c); 798 HexEncodeUtf16CodeUnit(writer, c);
833 } 799 }
834 else 800 else
835 { 801 {
836 // No handling of surrogates here - that's done earl ier 802 // No handling of surrogates here - that's done earl ier
837 builder.Append(c); 803 writer.Write(c);
838 } 804 }
839 break; 805 break;
840 } 806 }
841 } 807 }
808 writer.Write('"');
842 } 809 }
843 810
844 private const string Hex = "0123456789abcdef"; 811 private const string Hex = "0123456789abcdef";
845 private static void HexEncodeUtf16CodeUnit(StringBuilder builder, char c ) 812 private static void HexEncodeUtf16CodeUnit(TextWriter writer, char c)
846 { 813 {
847 builder.Append("\\u"); 814 writer.Write("\\u");
848 builder.Append(Hex[(c >> 12) & 0xf]); 815 writer.Write(Hex[(c >> 12) & 0xf]);
849 builder.Append(Hex[(c >> 8) & 0xf]); 816 writer.Write(Hex[(c >> 8) & 0xf]);
850 builder.Append(Hex[(c >> 4) & 0xf]); 817 writer.Write(Hex[(c >> 4) & 0xf]);
851 builder.Append(Hex[(c >> 0) & 0xf]); 818 writer.Write(Hex[(c >> 0) & 0xf]);
852 } 819 }
853 820
854 /// <summary> 821 /// <summary>
855 /// Settings controlling JSON formatting. 822 /// Settings controlling JSON formatting.
856 /// </summary> 823 /// </summary>
857 public sealed class Settings 824 public sealed class Settings
858 { 825 {
859 /// <summary> 826 /// <summary>
860 /// Default settings, as used by <see cref="JsonFormatter.Default"/> 827 /// Default settings, as used by <see cref="JsonFormatter.Default"/>
861 /// </summary> 828 /// </summary>
(...skipping 30 matching lines...) Expand all
892 859
893 /// <summary> 860 /// <summary>
894 /// Creates a new <see cref="Settings"/> object with the specified f ormatting of default values 861 /// Creates a new <see cref="Settings"/> object with the specified f ormatting of default values
895 /// and type registry. 862 /// and type registry.
896 /// </summary> 863 /// </summary>
897 /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param> 864 /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
898 /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param> 865 /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
899 public Settings(bool formatDefaultValues, TypeRegistry typeRegistry) 866 public Settings(bool formatDefaultValues, TypeRegistry typeRegistry)
900 { 867 {
901 FormatDefaultValues = formatDefaultValues; 868 FormatDefaultValues = formatDefaultValues;
902 TypeRegistry = Preconditions.CheckNotNull(typeRegistry, nameof(t ypeRegistry)); 869 TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nam eof(typeRegistry));
903 } 870 }
904 } 871 }
872
873 // Effectively a cache of mapping from enum values to the original name as specified in the proto file,
874 // fetched by reflection.
875 // The need for this is unfortunate, as is its unbounded size, but reali stically it shouldn't cause issues.
876 private static class OriginalEnumValueHelper
877 {
878 // TODO: In the future we might want to use ConcurrentDictionary, at the point where all
879 // the platforms we target have it.
880 private static readonly Dictionary<System.Type, Dictionary<object, s tring>> dictionaries
881 = new Dictionary<System.Type, Dictionary<object, string>>();
882
883 internal static string GetOriginalName(object value)
884 {
885 var enumType = value.GetType();
886 Dictionary<object, string> nameMapping;
887 lock (dictionaries)
888 {
889 if (!dictionaries.TryGetValue(enumType, out nameMapping))
890 {
891 nameMapping = GetNameMapping(enumType);
892 dictionaries[enumType] = nameMapping;
893 }
894 }
895
896 string originalName;
897 // If this returns false, originalName will be null, which is wh at we want.
898 nameMapping.TryGetValue(value, out originalName);
899 return originalName;
900 }
901
902 private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
903 enumType.GetTypeInfo().DeclaredFields
904 .Where(f => f.IsStatic)
905 .ToDictionary(f => f.GetValue(null),
906 f => f.GetCustomAttributes<OriginalNameAttribu te>()
907 .FirstOrDefault()
908 // If the attribute hasn't been applied, fall back to the name of the field.
909 ?.Name ?? f.Name);
910 }
905 } 911 }
906 } 912 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698