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

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

Issue 1842653006: Update //third_party/protobuf to version 3. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: merge Created 4 years, 8 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
(Empty)
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.Linq;
40
41 namespace Google.Protobuf
42 {
43 /// <summary>
44 /// Reflection-based converter from messages to JSON.
45 /// </summary>
46 /// <remarks>
47 /// <para>
48 /// Instances of this class are thread-safe, with no mutable state.
49 /// </para>
50 /// <para>
51 /// This is a simple start to get JSON formatting working. As it's reflectio n-based,
52 /// it's not as quick as baking calls into generated messages - but is a sim pler implementation.
53 /// (This code is generally not heavily optimized.)
54 /// </para>
55 /// </remarks>
56 public sealed class JsonFormatter
57 {
58 internal const string AnyTypeUrlField = "@type";
59 internal const string AnyDiagnosticValueField = "@value";
60 internal const string AnyWellKnownTypeValueField = "value";
61 private const string TypeUrlPrefix = "type.googleapis.com";
62 private const string NameValueSeparator = ": ";
63 private const string PropertySeparator = ", ";
64
65 /// <summary>
66 /// Returns a formatter using the default settings.
67 /// </summary>
68 public static JsonFormatter Default { get; } = new JsonFormatter(Setting s.Default);
69
70 // A JSON formatter which *only* exists
71 private static readonly JsonFormatter diagnosticFormatter = new JsonForm atter(Settings.Default);
72
73 /// <summary>
74 /// The JSON representation of the first 160 characters of Unicode.
75 /// Empty strings are replaced by the static constructor.
76 /// </summary>
77 private static readonly string[] CommonRepresentations = {
78 // C0 (ASCII and derivatives) control characters
79 "\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00
80 "\\u0004", "\\u0005", "\\u0006", "\\u0007",
81 "\\b", "\\t", "\\n", "\\u000b",
82 "\\f", "\\r", "\\u000e", "\\u000f",
83 "\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10
84 "\\u0014", "\\u0015", "\\u0016", "\\u0017",
85 "\\u0018", "\\u0019", "\\u001a", "\\u001b",
86 "\\u001c", "\\u001d", "\\u001e", "\\u001f",
87 // Escaping of " and \ are required by www.json.org string definitio n.
88 // Escaping of < and > are required for HTML security.
89 "", "", "\\\"", "", "", "", "", "", // 0x20
90 "", "", "", "", "", "", "", "",
91 "", "", "", "", "", "", "", "", // 0x30
92 "", "", "", "", "\\u003c", "", "\\u003e", "",
93 "", "", "", "", "", "", "", "", // 0x40
94 "", "", "", "", "", "", "", "",
95 "", "", "", "", "", "", "", "", // 0x50
96 "", "", "", "", "\\\\", "", "", "",
97 "", "", "", "", "", "", "", "", // 0x60
98 "", "", "", "", "", "", "", "",
99 "", "", "", "", "", "", "", "", // 0x70
100 "", "", "", "", "", "", "", "\\u007f",
101 // C1 (ISO 8859 and Unicode) extended control characters
102 "\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80
103 "\\u0084", "\\u0085", "\\u0086", "\\u0087",
104 "\\u0088", "\\u0089", "\\u008a", "\\u008b",
105 "\\u008c", "\\u008d", "\\u008e", "\\u008f",
106 "\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90
107 "\\u0094", "\\u0095", "\\u0096", "\\u0097",
108 "\\u0098", "\\u0099", "\\u009a", "\\u009b",
109 "\\u009c", "\\u009d", "\\u009e", "\\u009f"
110 };
111
112 static JsonFormatter()
113 {
114 for (int i = 0; i < CommonRepresentations.Length; i++)
115 {
116 if (CommonRepresentations[i] == "")
117 {
118 CommonRepresentations[i] = ((char) i).ToString();
119 }
120 }
121 }
122
123 private readonly Settings settings;
124
125 /// <summary>
126 /// Creates a new formatted with the given settings.
127 /// </summary>
128 /// <param name="settings">The settings.</param>
129 public JsonFormatter(Settings settings)
130 {
131 this.settings = settings;
132 }
133
134 /// <summary>
135 /// Formats the specified message as JSON.
136 /// </summary>
137 /// <param name="message">The message to format.</param>
138 /// <returns>The formatted message.</returns>
139 public string Format(IMessage message)
140 {
141 Preconditions.CheckNotNull(message, nameof(message));
142 StringBuilder builder = new StringBuilder();
143 if (message.Descriptor.IsWellKnownType)
144 {
145 WriteWellKnownTypeValue(builder, message.Descriptor, message, fa lse);
146 }
147 else
148 {
149 WriteMessage(builder, message);
150 }
151 return builder.ToString();
152 }
153
154 /// <summary>
155 /// Converts a message to JSON for diagnostic purposes with no extra con text.
156 /// </summary>
157 /// <remarks>
158 /// <para>
159 /// 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
161 /// 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>
163 /// is included with the base64 data from the <see cref="Any.Value"/> pr operty of the message.
164 /// </para>
165 /// <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
167 /// by other Protocol Buffer implementations.</para>
168 /// </remarks>
169 /// <param name="message">The message to format for diagnostic purposes. </param>
170 /// <returns>The diagnostic-only JSON representation of the message</ret urns>
171 public static string ToDiagnosticString(IMessage message)
172 {
173 Preconditions.CheckNotNull(message, nameof(message));
174 return diagnosticFormatter.Format(message);
175 }
176
177 private void WriteMessage(StringBuilder builder, IMessage message)
178 {
179 if (message == null)
180 {
181 WriteNull(builder);
182 return;
183 }
184 builder.Append("{ ");
185 bool writtenFields = WriteMessageFields(builder, message, false);
186 builder.Append(writtenFields ? " }" : "}");
187 }
188
189 private bool WriteMessageFields(StringBuilder builder, IMessage message, bool assumeFirstFieldWritten)
190 {
191 var fields = message.Descriptor.Fields;
192 bool first = !assumeFirstFieldWritten;
193 // First non-oneof fields
194 foreach (var field in fields.InFieldNumberOrder())
195 {
196 var accessor = field.Accessor;
197 if (field.ContainingOneof != null && field.ContainingOneof.Acces sor.GetCaseFieldDescriptor(message) != field)
198 {
199 continue;
200 }
201 // 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).
203 object value = accessor.GetValue(message);
204 if (field.ContainingOneof == null && !settings.FormatDefaultValu es && IsDefaultValue(accessor, value))
205 {
206 continue;
207 }
208 // Omit awkward (single) values such as unknown enum values
209 if (!field.IsRepeated && !field.IsMap && !CanWriteSingleValue(va lue))
210 {
211 continue;
212 }
213
214 // Okay, all tests complete: let's write the field value...
215 if (!first)
216 {
217 builder.Append(PropertySeparator);
218 }
219 WriteString(builder, ToCamelCase(accessor.Descriptor.Name));
220 builder.Append(NameValueSeparator);
221 WriteValue(builder, value);
222 first = false;
223 }
224 return !first;
225 }
226
227 // Converted from src/google/protobuf/util/internal/utility.cc ToCamelCa se
228 // TODO: Use the new field in FieldDescriptor.
229 internal static string ToCamelCase(string input)
230 {
231 bool capitalizeNext = false;
232 bool wasCap = true;
233 bool isCap = false;
234 bool firstWord = true;
235 StringBuilder result = new StringBuilder(input.Length);
236
237 for (int i = 0; i < input.Length; i++, wasCap = isCap)
238 {
239 isCap = char.IsUpper(input[i]);
240 if (input[i] == '_')
241 {
242 capitalizeNext = true;
243 if (result.Length != 0)
244 {
245 firstWord = false;
246 }
247 continue;
248 }
249 else if (firstWord)
250 {
251 // Consider when the current character B is capitalized,
252 // first word ends when:
253 // 1) following a lowercase: "...aB..."
254 // 2) followed by a lowercase: "...ABc..."
255 if (result.Length != 0 && isCap &&
256 (!wasCap || (i + 1 < input.Length && char.IsLower(input[ i + 1]))))
257 {
258 firstWord = false;
259 }
260 else
261 {
262 result.Append(char.ToLowerInvariant(input[i]));
263 continue;
264 }
265 }
266 else if (capitalizeNext)
267 {
268 capitalizeNext = false;
269 if (char.IsLower(input[i]))
270 {
271 result.Append(char.ToUpperInvariant(input[i]));
272 continue;
273 }
274 }
275 result.Append(input[i]);
276 }
277 return result.ToString();
278 }
279
280 private static void WriteNull(StringBuilder builder)
281 {
282 builder.Append("null");
283 }
284
285 private static bool IsDefaultValue(IFieldAccessor accessor, object value )
286 {
287 if (accessor.Descriptor.IsMap)
288 {
289 IDictionary dictionary = (IDictionary) value;
290 return dictionary.Count == 0;
291 }
292 if (accessor.Descriptor.IsRepeated)
293 {
294 IList list = (IList) value;
295 return list.Count == 0;
296 }
297 switch (accessor.Descriptor.FieldType)
298 {
299 case FieldType.Bool:
300 return (bool) value == false;
301 case FieldType.Bytes:
302 return (ByteString) value == ByteString.Empty;
303 case FieldType.String:
304 return (string) value == "";
305 case FieldType.Double:
306 return (double) value == 0.0;
307 case FieldType.SInt32:
308 case FieldType.Int32:
309 case FieldType.SFixed32:
310 case FieldType.Enum:
311 return (int) value == 0;
312 case FieldType.Fixed32:
313 case FieldType.UInt32:
314 return (uint) value == 0;
315 case FieldType.Fixed64:
316 case FieldType.UInt64:
317 return (ulong) value == 0;
318 case FieldType.SFixed64:
319 case FieldType.Int64:
320 case FieldType.SInt64:
321 return (long) value == 0;
322 case FieldType.Float:
323 return (float) value == 0f;
324 case FieldType.Message:
325 case FieldType.Group: // Never expect to get this, but...
326 return value == null;
327 default:
328 throw new ArgumentException("Invalid field type");
329 }
330 }
331
332 private void WriteValue(StringBuilder builder, object value)
333 {
334 if (value == null)
335 {
336 WriteNull(builder);
337 }
338 else if (value is bool)
339 {
340 builder.Append((bool) value ? "true" : "false");
341 }
342 else if (value is ByteString)
343 {
344 // Nothing in Base64 needs escaping
345 builder.Append('"');
346 builder.Append(((ByteString) value).ToBase64());
347 builder.Append('"');
348 }
349 else if (value is string)
350 {
351 WriteString(builder, (string) value);
352 }
353 else if (value is IDictionary)
354 {
355 WriteDictionary(builder, (IDictionary) value);
356 }
357 else if (value is IList)
358 {
359 WriteList(builder, (IList) value);
360 }
361 else if (value is int || value is uint)
362 {
363 IFormattable formattable = (IFormattable) value;
364 builder.Append(formattable.ToString("d", CultureInfo.InvariantCu lture));
365 }
366 else if (value is long || value is ulong)
367 {
368 builder.Append('"');
369 IFormattable formattable = (IFormattable) value;
370 builder.Append(formattable.ToString("d", CultureInfo.InvariantCu lture));
371 builder.Append('"');
372 }
373 else if (value is System.Enum)
374 {
375 WriteString(builder, value.ToString());
376 }
377 else if (value is float || value is double)
378 {
379 string text = ((IFormattable) value).ToString("r", CultureInfo.I nvariantCulture);
380 if (text == "NaN" || text == "Infinity" || text == "-Infinity")
381 {
382 builder.Append('"');
383 builder.Append(text);
384 builder.Append('"');
385 }
386 else
387 {
388 builder.Append(text);
389 }
390 }
391 else if (value is IMessage)
392 {
393 IMessage message = (IMessage) value;
394 if (message.Descriptor.IsWellKnownType)
395 {
396 WriteWellKnownTypeValue(builder, message.Descriptor, value, true);
397 }
398 else
399 {
400 WriteMessage(builder, (IMessage) value);
401 }
402 }
403 else
404 {
405 throw new ArgumentException("Unable to format value of type " + value.GetType());
406 }
407 }
408
409 /// <summary>
410 /// 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
412 /// values are using the embedded well-known types, in order to allow fo r dynamic messages
413 /// in the future.
414 /// </summary>
415 private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescr iptor descriptor, object value, bool inField)
416 {
417 // 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.
419 if (value == null)
420 {
421 WriteNull(builder);
422 return;
423 }
424 // 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).
426 // 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...
428 // WriteValue will do the right thing.)
429 if (descriptor.IsWrapperType)
430 {
431 if (value is IMessage)
432 {
433 var message = (IMessage) value;
434 value = message.Descriptor.Fields[WrappersReflection.Wrapper ValueFieldNumber].Accessor.GetValue(message);
435 }
436 WriteValue(builder, value);
437 return;
438 }
439 if (descriptor.FullName == Timestamp.Descriptor.FullName)
440 {
441 MaybeWrapInString(builder, value, WriteTimestamp, inField);
442 return;
443 }
444 if (descriptor.FullName == Duration.Descriptor.FullName)
445 {
446 MaybeWrapInString(builder, value, WriteDuration, inField);
447 return;
448 }
449 if (descriptor.FullName == FieldMask.Descriptor.FullName)
450 {
451 MaybeWrapInString(builder, value, WriteFieldMask, inField);
452 return;
453 }
454 if (descriptor.FullName == Struct.Descriptor.FullName)
455 {
456 WriteStruct(builder, (IMessage) value);
457 return;
458 }
459 if (descriptor.FullName == ListValue.Descriptor.FullName)
460 {
461 var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumbe r].Accessor;
462 WriteList(builder, (IList) fieldAccessor.GetValue((IMessage) val ue));
463 return;
464 }
465 if (descriptor.FullName == Value.Descriptor.FullName)
466 {
467 WriteStructFieldValue(builder, (IMessage) value);
468 return;
469 }
470 if (descriptor.FullName == Any.Descriptor.FullName)
471 {
472 WriteAny(builder, (IMessage) value);
473 return;
474 }
475 WriteMessage(builder, (IMessage) value);
476 }
477
478 /// <summary>
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 {
498 // 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
500 // 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.
502 int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber ].Accessor.GetValue(value);
503 long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsField Number].Accessor.GetValue(value);
504
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 }
514
515 private void WriteDuration(StringBuilder builder, IMessage value)
516 {
517 // TODO: Same as for WriteTimestamp
518 int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber] .Accessor.GetValue(value);
519 long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldN umber].Accessor.GetValue(value);
520
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 }
535
536 private void WriteFieldMask(StringBuilder builder, IMessage value)
537 {
538 IList paths = (IList) value.Descriptor.Fields[FieldMask.PathsFieldNu mber].Accessor.GetValue(value);
539 AppendEscapedString(builder, string.Join(",", paths.Cast<string>().S elect(ToCamelCase)));
540 }
541
542 private void WriteAny(StringBuilder builder, IMessage value)
543 {
544 if (ReferenceEquals(this, diagnosticFormatter))
545 {
546 WriteDiagnosticOnlyAny(builder, value);
547 return;
548 }
549
550 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);
552 string typeName = GetTypeName(typeUrl);
553 MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
554 if (descriptor == null)
555 {
556 throw new InvalidOperationException($"Type registry has no descr iptor for type name '{typeName}'");
557 }
558 IMessage message = descriptor.Parser.ParseFrom(data);
559 builder.Append("{ ");
560 WriteString(builder, AnyTypeUrlField);
561 builder.Append(NameValueSeparator);
562 WriteString(builder, typeUrl);
563
564 if (descriptor.IsWellKnownType)
565 {
566 builder.Append(PropertySeparator);
567 WriteString(builder, AnyWellKnownTypeValueField);
568 builder.Append(NameValueSeparator);
569 WriteWellKnownTypeValue(builder, descriptor, message, true);
570 }
571 else
572 {
573 WriteMessageFields(builder, message, true);
574 }
575 builder.Append(" }");
576 }
577
578 private void WriteDiagnosticOnlyAny(StringBuilder builder, IMessage valu e)
579 {
580 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);
582 builder.Append("{ ");
583 WriteString(builder, AnyTypeUrlField);
584 builder.Append(NameValueSeparator);
585 WriteString(builder, typeUrl);
586 builder.Append(PropertySeparator);
587 WriteString(builder, AnyDiagnosticValueField);
588 builder.Append(NameValueSeparator);
589 builder.Append('"');
590 builder.Append(data.ToBase64());
591 builder.Append('"');
592 builder.Append(" }");
593 }
594
595 internal static string GetTypeName(String typeUrl)
596 {
597 string[] parts = typeUrl.Split('/');
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);
634 bool first = true;
635 foreach (DictionaryEntry entry in fields)
636 {
637 string key = (string) entry.Key;
638 IMessage value = (IMessage) entry.Value;
639 if (string.IsNullOrEmpty(key) || value == null)
640 {
641 throw new InvalidOperationException("Struct fields cannot ha ve an empty key or a null value.");
642 }
643
644 if (!first)
645 {
646 builder.Append(PropertySeparator);
647 }
648 WriteString(builder, key);
649 builder.Append(NameValueSeparator);
650 WriteStructFieldValue(builder, value);
651 first = false;
652 }
653 builder.Append(first ? "}" : " }");
654 }
655
656 private void WriteStructFieldValue(StringBuilder builder, IMessage messa ge)
657 {
658 var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFi eldDescriptor(message);
659 if (specifiedField == null)
660 {
661 throw new InvalidOperationException("Value message must contain a value for the oneof.");
662 }
663
664 object value = specifiedField.Accessor.GetValue(message);
665
666 switch (specifiedField.FieldNumber)
667 {
668 case Value.BoolValueFieldNumber:
669 case Value.StringValueFieldNumber:
670 case Value.NumberValueFieldNumber:
671 WriteValue(builder, value);
672 return;
673 case Value.StructValueFieldNumber:
674 case Value.ListValueFieldNumber:
675 // Structs and ListValues are nested messages, and already w ell-known types.
676 var nestedMessage = (IMessage) specifiedField.Accessor.GetVa lue(message);
677 WriteWellKnownTypeValue(builder, nestedMessage.Descriptor, n estedMessage, true);
678 return;
679 case Value.NullValueFieldNumber:
680 WriteNull(builder);
681 return;
682 default:
683 throw new InvalidOperationException("Unexpected case in stru ct field: " + specifiedField.FieldNumber);
684 }
685 }
686
687 internal void WriteList(StringBuilder builder, IList list)
688 {
689 builder.Append("[ ");
690 bool first = true;
691 foreach (var value in list)
692 {
693 if (!CanWriteSingleValue(value))
694 {
695 continue;
696 }
697 if (!first)
698 {
699 builder.Append(PropertySeparator);
700 }
701 WriteValue(builder, value);
702 first = false;
703 }
704 builder.Append(first ? "]" : " ]");
705 }
706
707 internal void WriteDictionary(StringBuilder builder, IDictionary diction ary)
708 {
709 builder.Append("{ ");
710 bool first = true;
711 // This will box each pair. Could use IDictionaryEnumerator, but tha t's ugly in terms of disposal.
712 foreach (DictionaryEntry pair in dictionary)
713 {
714 if (!CanWriteSingleValue(pair.Value))
715 {
716 continue;
717 }
718 if (!first)
719 {
720 builder.Append(PropertySeparator);
721 }
722 string keyText;
723 if (pair.Key is string)
724 {
725 keyText = (string) pair.Key;
726 }
727 else if (pair.Key is bool)
728 {
729 keyText = (bool) pair.Key ? "true" : "false";
730 }
731 else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
732 {
733 keyText = ((IFormattable) pair.Key).ToString("d", CultureInf o.InvariantCulture);
734 }
735 else
736 {
737 if (pair.Key == null)
738 {
739 throw new ArgumentException("Dictionary has entry with n ull key");
740 }
741 throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
742 }
743 WriteString(builder, keyText);
744 builder.Append(NameValueSeparator);
745 WriteValue(builder, pair.Value);
746 first = false;
747 }
748 builder.Append(first ? "}" : " }");
749 }
750
751 /// <summary>
752 /// 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.
754 /// For repeated/map fields, this always returns true.
755 /// </summary>
756 private bool CanWriteSingleValue(object value)
757 {
758 if (value is System.Enum)
759 {
760 return System.Enum.IsDefined(value.GetType(), value);
761 }
762 return true;
763 }
764
765 /// <summary>
766 /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
767 /// </summary>
768 /// <remarks>
769 /// Other than surrogate pair handling, this code is mostly taken from s rc/google/protobuf/util/internal/json_escaping.cc.
770 /// </remarks>
771 private void WriteString(StringBuilder builder, string text)
772 {
773 builder.Append('"');
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++)
784 {
785 char c = text[i];
786 if (c < 0xa0)
787 {
788 builder.Append(CommonRepresentations[c]);
789 continue;
790 }
791 if (char.IsHighSurrogate(c))
792 {
793 // Encountered first part of a surrogate pair.
794 // Check that we have the whole pair, and encode both parts as hex.
795 i++;
796 if (i == text.Length || !char.IsLowSurrogate(text[i]))
797 {
798 throw new ArgumentException("String contains low surroga te not followed by high surrogate");
799 }
800 HexEncodeUtf16CodeUnit(builder, c);
801 HexEncodeUtf16CodeUnit(builder, text[i]);
802 continue;
803 }
804 else if (char.IsLowSurrogate(c))
805 {
806 throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
807 }
808 switch ((uint) c)
809 {
810 // These are not required by json spec
811 // but used to prevent security bugs in javascript.
812 case 0xfeff: // Zero width no-break space
813 case 0xfff9: // Interlinear annotation anchor
814 case 0xfffa: // Interlinear annotation separator
815 case 0xfffb: // Interlinear annotation terminator
816
817 case 0x00ad: // Soft-hyphen
818 case 0x06dd: // Arabic end of ayah
819 case 0x070f: // Syriac abbreviation mark
820 case 0x17b4: // Khmer vowel inherent Aq
821 case 0x17b5: // Khmer vowel inherent Aa
822 HexEncodeUtf16CodeUnit(builder, c);
823 break;
824
825 default:
826 if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs
827 (c >= 0x200b && c <= 0x200f) || // Zero width etc.
828 (c >= 0x2028 && c <= 0x202e) || // Separators etc.
829 (c >= 0x2060 && c <= 0x2064) || // Invisible etc.
830 (c >= 0x206a && c <= 0x206f))
831 {
832 HexEncodeUtf16CodeUnit(builder, c);
833 }
834 else
835 {
836 // No handling of surrogates here - that's done earl ier
837 builder.Append(c);
838 }
839 break;
840 }
841 }
842 }
843
844 private const string Hex = "0123456789abcdef";
845 private static void HexEncodeUtf16CodeUnit(StringBuilder builder, char c )
846 {
847 builder.Append("\\u");
848 builder.Append(Hex[(c >> 12) & 0xf]);
849 builder.Append(Hex[(c >> 8) & 0xf]);
850 builder.Append(Hex[(c >> 4) & 0xf]);
851 builder.Append(Hex[(c >> 0) & 0xf]);
852 }
853
854 /// <summary>
855 /// Settings controlling JSON formatting.
856 /// </summary>
857 public sealed class Settings
858 {
859 /// <summary>
860 /// Default settings, as used by <see cref="JsonFormatter.Default"/>
861 /// </summary>
862 public static Settings Default { get; }
863
864 // Workaround for the Mono compiler complaining about XML comments n ot being on
865 // valid language elements.
866 static Settings()
867 {
868 Default = new Settings(false);
869 }
870
871 /// <summary>
872 /// Whether fields whose values are the default for the field type ( e.g. 0 for integers)
873 /// should be formatted (true) or omitted (false).
874 /// </summary>
875 public bool FormatDefaultValues { get; }
876
877 /// <summary>
878 /// The type registry used to format <see cref="Any"/> messages.
879 /// </summary>
880 public TypeRegistry TypeRegistry { get; }
881
882 // TODO: Work out how we're going to scale this to multiple settings . "WithXyz" methods?
883
884 /// <summary>
885 /// Creates a new <see cref="Settings"/> object with the specified f ormatting of default values
886 /// and an empty type registry.
887 /// </summary>
888 /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
889 public Settings(bool formatDefaultValues) : this(formatDefaultValues , TypeRegistry.Empty)
890 {
891 }
892
893 /// <summary>
894 /// Creates a new <see cref="Settings"/> object with the specified f ormatting of default values
895 /// and type registry.
896 /// </summary>
897 /// <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>
899 public Settings(bool formatDefaultValues, TypeRegistry typeRegistry)
900 {
901 FormatDefaultValues = formatDefaultValues;
902 TypeRegistry = Preconditions.CheckNotNull(typeRegistry, nameof(t ypeRegistry));
903 }
904 }
905 }
906 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698