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

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

Issue 1322483002: Revert https://codereview.chromium.org/1291903002 (protobuf roll). (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 3 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 private static JsonFormatter defaultInstance = new JsonFormatter(Setting s.Default);
59
60 /// <summary>
61 /// Returns a formatter using the default settings.
62 /// </summary>
63 public static JsonFormatter Default { get { return defaultInstance; } }
64
65 /// <summary>
66 /// The JSON representation of the first 160 characters of Unicode.
67 /// Empty strings are replaced by the static constructor.
68 /// </summary>
69 private static readonly string[] CommonRepresentations = {
70 // C0 (ASCII and derivatives) control characters
71 "\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00
72 "\\u0004", "\\u0005", "\\u0006", "\\u0007",
73 "\\b", "\\t", "\\n", "\\u000b",
74 "\\f", "\\r", "\\u000e", "\\u000f",
75 "\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10
76 "\\u0014", "\\u0015", "\\u0016", "\\u0017",
77 "\\u0018", "\\u0019", "\\u001a", "\\u001b",
78 "\\u001c", "\\u001d", "\\u001e", "\\u001f",
79 // Escaping of " and \ are required by www.json.org string definitio n.
80 // Escaping of < and > are required for HTML security.
81 "", "", "\\\"", "", "", "", "", "", // 0x20
82 "", "", "", "", "", "", "", "",
83 "", "", "", "", "", "", "", "", // 0x30
84 "", "", "", "", "\\u003c", "", "\\u003e", "",
85 "", "", "", "", "", "", "", "", // 0x40
86 "", "", "", "", "", "", "", "",
87 "", "", "", "", "", "", "", "", // 0x50
88 "", "", "", "", "\\\\", "", "", "",
89 "", "", "", "", "", "", "", "", // 0x60
90 "", "", "", "", "", "", "", "",
91 "", "", "", "", "", "", "", "", // 0x70
92 "", "", "", "", "", "", "", "\\u007f",
93 // C1 (ISO 8859 and Unicode) extended control characters
94 "\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80
95 "\\u0084", "\\u0085", "\\u0086", "\\u0087",
96 "\\u0088", "\\u0089", "\\u008a", "\\u008b",
97 "\\u008c", "\\u008d", "\\u008e", "\\u008f",
98 "\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90
99 "\\u0094", "\\u0095", "\\u0096", "\\u0097",
100 "\\u0098", "\\u0099", "\\u009a", "\\u009b",
101 "\\u009c", "\\u009d", "\\u009e", "\\u009f"
102 };
103
104 static JsonFormatter()
105 {
106 for (int i = 0; i < CommonRepresentations.Length; i++)
107 {
108 if (CommonRepresentations[i] == "")
109 {
110 CommonRepresentations[i] = ((char) i).ToString();
111 }
112 }
113 }
114
115 private readonly Settings settings;
116
117 /// <summary>
118 /// Creates a new formatted with the given settings.
119 /// </summary>
120 /// <param name="settings">The settings.</param>
121 public JsonFormatter(Settings settings)
122 {
123 this.settings = settings;
124 }
125
126 /// <summary>
127 /// Formats the specified message as JSON.
128 /// </summary>
129 /// <param name="message">The message to format.</param>
130 /// <returns>The formatted message.</returns>
131 public string Format(IMessage message)
132 {
133 Preconditions.CheckNotNull(message, "message");
134 StringBuilder builder = new StringBuilder();
135 if (message.Descriptor.IsWellKnownType)
136 {
137 WriteWellKnownTypeValue(builder, message.Descriptor, message, fa lse);
138 }
139 else
140 {
141 WriteMessage(builder, message);
142 }
143 return builder.ToString();
144 }
145
146 private void WriteMessage(StringBuilder builder, IMessage message)
147 {
148 if (message == null)
149 {
150 WriteNull(builder);
151 return;
152 }
153 builder.Append("{ ");
154 var fields = message.Descriptor.Fields;
155 bool first = true;
156 // First non-oneof fields
157 foreach (var field in fields.InFieldNumberOrder())
158 {
159 var accessor = field.Accessor;
160 // Oneofs are written later
161 if (field.ContainingOneof != null && field.ContainingOneof.Acces sor.GetCaseFieldDescriptor(message) != field)
162 {
163 continue;
164 }
165 // Omit default values unless we're asked to format them, or the y're oneofs (where the default
166 // value is still formatted regardless, because that's how we pr eserve the oneof case).
167 object value = accessor.GetValue(message);
168 if (field.ContainingOneof == null && !settings.FormatDefaultValu es && IsDefaultValue(accessor, value))
169 {
170 continue;
171 }
172 // Omit awkward (single) values such as unknown enum values
173 if (!field.IsRepeated && !field.IsMap && !CanWriteSingleValue(ac cessor.Descriptor, value))
174 {
175 continue;
176 }
177
178 // Okay, all tests complete: let's write the field value...
179 if (!first)
180 {
181 builder.Append(", ");
182 }
183 WriteString(builder, ToCamelCase(accessor.Descriptor.Name));
184 builder.Append(": ");
185 WriteValue(builder, accessor, value);
186 first = false;
187 }
188 builder.Append(first ? "}" : " }");
189 }
190
191 // Converted from src/google/protobuf/util/internal/utility.cc ToCamelCa se
192 internal static string ToCamelCase(string input)
193 {
194 bool capitalizeNext = false;
195 bool wasCap = true;
196 bool isCap = false;
197 bool firstWord = true;
198 StringBuilder result = new StringBuilder(input.Length);
199
200 for (int i = 0; i < input.Length; i++, wasCap = isCap)
201 {
202 isCap = char.IsUpper(input[i]);
203 if (input[i] == '_')
204 {
205 capitalizeNext = true;
206 if (result.Length != 0)
207 {
208 firstWord = false;
209 }
210 continue;
211 }
212 else if (firstWord)
213 {
214 // Consider when the current character B is capitalized,
215 // first word ends when:
216 // 1) following a lowercase: "...aB..."
217 // 2) followed by a lowercase: "...ABc..."
218 if (result.Length != 0 && isCap &&
219 (!wasCap || (i + 1 < input.Length && char.IsLower(input[ i + 1]))))
220 {
221 firstWord = false;
222 }
223 else
224 {
225 result.Append(char.ToLowerInvariant(input[i]));
226 continue;
227 }
228 }
229 else if (capitalizeNext)
230 {
231 capitalizeNext = false;
232 if (char.IsLower(input[i]))
233 {
234 result.Append(char.ToUpperInvariant(input[i]));
235 continue;
236 }
237 }
238 result.Append(input[i]);
239 }
240 return result.ToString();
241 }
242
243 private static void WriteNull(StringBuilder builder)
244 {
245 builder.Append("null");
246 }
247
248 private static bool IsDefaultValue(IFieldAccessor accessor, object value )
249 {
250 if (accessor.Descriptor.IsMap)
251 {
252 IDictionary dictionary = (IDictionary) value;
253 return dictionary.Count == 0;
254 }
255 if (accessor.Descriptor.IsRepeated)
256 {
257 IList list = (IList) value;
258 return list.Count == 0;
259 }
260 switch (accessor.Descriptor.FieldType)
261 {
262 case FieldType.Bool:
263 return (bool) value == false;
264 case FieldType.Bytes:
265 return (ByteString) value == ByteString.Empty;
266 case FieldType.String:
267 return (string) value == "";
268 case FieldType.Double:
269 return (double) value == 0.0;
270 case FieldType.SInt32:
271 case FieldType.Int32:
272 case FieldType.SFixed32:
273 case FieldType.Enum:
274 return (int) value == 0;
275 case FieldType.Fixed32:
276 case FieldType.UInt32:
277 return (uint) value == 0;
278 case FieldType.Fixed64:
279 case FieldType.UInt64:
280 return (ulong) value == 0;
281 case FieldType.SFixed64:
282 case FieldType.Int64:
283 case FieldType.SInt64:
284 return (long) value == 0;
285 case FieldType.Float:
286 return (float) value == 0f;
287 case FieldType.Message:
288 case FieldType.Group: // Never expect to get this, but...
289 return value == null;
290 default:
291 throw new ArgumentException("Invalid field type");
292 }
293 }
294
295 private void WriteValue(StringBuilder builder, IFieldAccessor accessor, object value)
296 {
297 if (accessor.Descriptor.IsMap)
298 {
299 WriteDictionary(builder, accessor, (IDictionary) value);
300 }
301 else if (accessor.Descriptor.IsRepeated)
302 {
303 WriteList(builder, accessor, (IList) value);
304 }
305 else
306 {
307 WriteSingleValue(builder, accessor.Descriptor, value);
308 }
309 }
310
311 private void WriteSingleValue(StringBuilder builder, FieldDescriptor des criptor, object value)
312 {
313 switch (descriptor.FieldType)
314 {
315 case FieldType.Bool:
316 builder.Append((bool) value ? "true" : "false");
317 break;
318 case FieldType.Bytes:
319 // Nothing in Base64 needs escaping
320 builder.Append('"');
321 builder.Append(((ByteString) value).ToBase64());
322 builder.Append('"');
323 break;
324 case FieldType.String:
325 WriteString(builder, (string) value);
326 break;
327 case FieldType.Fixed32:
328 case FieldType.UInt32:
329 case FieldType.SInt32:
330 case FieldType.Int32:
331 case FieldType.SFixed32:
332 {
333 IFormattable formattable = (IFormattable) value;
334 builder.Append(formattable.ToString("d", CultureInfo.Inv ariantCulture));
335 break;
336 }
337 case FieldType.Enum:
338 EnumValueDescriptor enumValue = descriptor.EnumType.FindValu eByNumber((int) value);
339 // We will already have validated that this is a known value .
340 WriteString(builder, enumValue.Name);
341 break;
342 case FieldType.Fixed64:
343 case FieldType.UInt64:
344 case FieldType.SFixed64:
345 case FieldType.Int64:
346 case FieldType.SInt64:
347 {
348 builder.Append('"');
349 IFormattable formattable = (IFormattable) value;
350 builder.Append(formattable.ToString("d", CultureInfo.Inv ariantCulture));
351 builder.Append('"');
352 break;
353 }
354 case FieldType.Double:
355 case FieldType.Float:
356 string text = ((IFormattable) value).ToString("r", CultureIn fo.InvariantCulture);
357 if (text == "NaN" || text == "Infinity" || text == "-Infinit y")
358 {
359 builder.Append('"');
360 builder.Append(text);
361 builder.Append('"');
362 }
363 else
364 {
365 builder.Append(text);
366 }
367 break;
368 case FieldType.Message:
369 case FieldType.Group: // Never expect to get this, but...
370 if (descriptor.MessageType.IsWellKnownType)
371 {
372 WriteWellKnownTypeValue(builder, descriptor.MessageType, value, true);
373 }
374 else
375 {
376 WriteMessage(builder, (IMessage) value);
377 }
378 break;
379 default:
380 throw new ArgumentException("Invalid field type: " + descrip tor.FieldType);
381 }
382 }
383
384 /// <summary>
385 /// Central interception point for well-known type formatting. Any well- known types which
386 /// don't need special handling can fall back to WriteMessage. We avoid assuming that the
387 /// values are using the embedded well-known types, in order to allow fo r dynamic messages
388 /// in the future.
389 /// </summary>
390 private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescr iptor descriptor, object value, bool inField)
391 {
392 if (value == null)
393 {
394 WriteNull(builder);
395 return;
396 }
397 // For wrapper types, the value will be the (possibly boxed) "native " value,
398 // so we can write it as if we were unconditionally writing the Valu e field for the wrapper type.
399 if (descriptor.File == Int32Value.Descriptor.File)
400 {
401 WriteSingleValue(builder, descriptor.FindFieldByNumber(1), value );
402 return;
403 }
404 if (descriptor.FullName == Timestamp.Descriptor.FullName)
405 {
406 MaybeWrapInString(builder, value, WriteTimestamp, inField);
407 return;
408 }
409 if (descriptor.FullName == Duration.Descriptor.FullName)
410 {
411 MaybeWrapInString(builder, value, WriteDuration, inField);
412 return;
413 }
414 if (descriptor.FullName == FieldMask.Descriptor.FullName)
415 {
416 MaybeWrapInString(builder, value, WriteFieldMask, inField);
417 return;
418 }
419 if (descriptor.FullName == Struct.Descriptor.FullName)
420 {
421 WriteStruct(builder, (IMessage) value);
422 return;
423 }
424 if (descriptor.FullName == ListValue.Descriptor.FullName)
425 {
426 var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumbe r].Accessor;
427 WriteList(builder, fieldAccessor, (IList) fieldAccessor.GetValue ((IMessage) value));
428 return;
429 }
430 if (descriptor.FullName == Value.Descriptor.FullName)
431 {
432 WriteStructFieldValue(builder, (IMessage) value);
433 return;
434 }
435 WriteMessage(builder, (IMessage) value);
436 }
437
438 /// <summary>
439 /// Some well-known types end up as string values... so they need wrappi ng in quotes, but only
440 /// when they're being used as fields within another message.
441 /// </summary>
442 private void MaybeWrapInString(StringBuilder builder, object value, Acti on<StringBuilder, IMessage> action, bool inField)
443 {
444 if (inField)
445 {
446 builder.Append('"');
447 action(builder, (IMessage) value);
448 builder.Append('"');
449 }
450 else
451 {
452 action(builder, (IMessage) value);
453 }
454 }
455
456 private void WriteTimestamp(StringBuilder builder, IMessage value)
457 {
458 // TODO: In the common case where this *is* using the built-in Times tamp type, we could
459 // avoid all the reflection at this point, by casting to Timestamp. In the interests of
460 // avoiding subtle bugs, don't do that until we've implemented Dynam icMessage so that we can prove
461 // it still works in that case.
462 int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber ].Accessor.GetValue(value);
463 long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsField Number].Accessor.GetValue(value);
464
465 // Even if the original message isn't using the built-in classes, we can still build one... and then
466 // rely on it being normalized.
467 Timestamp normalized = Timestamp.Normalize(seconds, nanos);
468 // Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
469 DateTime dateTime = normalized.ToDateTime();
470 builder.Append(dateTime.ToString("yyyy'-'MM'-'dd'T'HH:mm:ss", Cultur eInfo.InvariantCulture));
471 AppendNanoseconds(builder, Math.Abs(normalized.Nanos));
472 builder.Append('Z');
473 }
474
475 private void WriteDuration(StringBuilder builder, IMessage value)
476 {
477 // TODO: Same as for WriteTimestamp
478 int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber] .Accessor.GetValue(value);
479 long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldN umber].Accessor.GetValue(value);
480
481 // Even if the original message isn't using the built-in classes, we can still build one... and then
482 // rely on it being normalized.
483 Duration normalized = Duration.Normalize(seconds, nanos);
484
485 // The seconds part will normally provide the minus sign if we need it, but not if it's 0...
486 if (normalized.Seconds == 0 && normalized.Nanos < 0)
487 {
488 builder.Append('-');
489 }
490
491 builder.Append(normalized.Seconds.ToString("d", CultureInfo.Invarian tCulture));
492 AppendNanoseconds(builder, Math.Abs(normalized.Nanos));
493 builder.Append('s');
494 }
495
496 private void WriteFieldMask(StringBuilder builder, IMessage value)
497 {
498 IList paths = (IList) value.Descriptor.Fields[FieldMask.PathsFieldNu mber].Accessor.GetValue(value);
499 AppendEscapedString(builder, string.Join(",", paths.Cast<string>().S elect(ToCamelCase)));
500 }
501
502 /// <summary>
503 /// Appends a number of nanoseconds to a StringBuilder. Either 0 digits are added (in which
504 /// case no "." is appended), or 3 6 or 9 digits.
505 /// </summary>
506 private static void AppendNanoseconds(StringBuilder builder, int nanos)
507 {
508 if (nanos != 0)
509 {
510 builder.Append('.');
511 // Output to 3, 6 or 9 digits.
512 if (nanos % 1000000 == 0)
513 {
514 builder.Append((nanos / 1000000).ToString("d", CultureInfo.I nvariantCulture));
515 }
516 else if (nanos % 1000 == 0)
517 {
518 builder.Append((nanos / 1000).ToString("d", CultureInfo.Inva riantCulture));
519 }
520 else
521 {
522 builder.Append(nanos.ToString("d", CultureInfo.InvariantCult ure));
523 }
524 }
525 }
526
527 private void WriteStruct(StringBuilder builder, IMessage message)
528 {
529 builder.Append("{ ");
530 IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct. FieldsFieldNumber].Accessor.GetValue(message);
531 bool first = true;
532 foreach (DictionaryEntry entry in fields)
533 {
534 string key = (string) entry.Key;
535 IMessage value = (IMessage) entry.Value;
536 if (string.IsNullOrEmpty(key) || value == null)
537 {
538 throw new InvalidOperationException("Struct fields cannot ha ve an empty key or a null value.");
539 }
540
541 if (!first)
542 {
543 builder.Append(", ");
544 }
545 WriteString(builder, key);
546 builder.Append(": ");
547 WriteStructFieldValue(builder, value);
548 first = false;
549 }
550 builder.Append(first ? "}" : " }");
551 }
552
553 private void WriteStructFieldValue(StringBuilder builder, IMessage messa ge)
554 {
555 var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFi eldDescriptor(message);
556 if (specifiedField == null)
557 {
558 throw new InvalidOperationException("Value message must contain a value for the oneof.");
559 }
560
561 object value = specifiedField.Accessor.GetValue(message);
562
563 switch (specifiedField.FieldNumber)
564 {
565 case Value.BoolValueFieldNumber:
566 case Value.StringValueFieldNumber:
567 case Value.NumberValueFieldNumber:
568 WriteSingleValue(builder, specifiedField, value);
569 return;
570 case Value.StructValueFieldNumber:
571 case Value.ListValueFieldNumber:
572 // Structs and ListValues are nested messages, and already w ell-known types.
573 var nestedMessage = (IMessage) specifiedField.Accessor.GetVa lue(message);
574 WriteWellKnownTypeValue(builder, nestedMessage.Descriptor, n estedMessage, true);
575 return;
576 case Value.NullValueFieldNumber:
577 WriteNull(builder);
578 return;
579 default:
580 throw new InvalidOperationException("Unexpected case in stru ct field: " + specifiedField.FieldNumber);
581 }
582 }
583
584 private void WriteList(StringBuilder builder, IFieldAccessor accessor, I List list)
585 {
586 builder.Append("[ ");
587 bool first = true;
588 foreach (var value in list)
589 {
590 if (!CanWriteSingleValue(accessor.Descriptor, value))
591 {
592 continue;
593 }
594 if (!first)
595 {
596 builder.Append(", ");
597 }
598 WriteSingleValue(builder, accessor.Descriptor, value);
599 first = false;
600 }
601 builder.Append(first ? "]" : " ]");
602 }
603
604 private void WriteDictionary(StringBuilder builder, IFieldAccessor acces sor, IDictionary dictionary)
605 {
606 builder.Append("{ ");
607 bool first = true;
608 FieldDescriptor keyType = accessor.Descriptor.MessageType.FindFieldB yNumber(1);
609 FieldDescriptor valueType = accessor.Descriptor.MessageType.FindFiel dByNumber(2);
610 // This will box each pair. Could use IDictionaryEnumerator, but tha t's ugly in terms of disposal.
611 foreach (DictionaryEntry pair in dictionary)
612 {
613 if (!CanWriteSingleValue(valueType, pair.Value))
614 {
615 continue;
616 }
617 if (!first)
618 {
619 builder.Append(", ");
620 }
621 string keyText;
622 switch (keyType.FieldType)
623 {
624 case FieldType.String:
625 keyText = (string) pair.Key;
626 break;
627 case FieldType.Bool:
628 keyText = (bool) pair.Key ? "true" : "false";
629 break;
630 case FieldType.Fixed32:
631 case FieldType.Fixed64:
632 case FieldType.SFixed32:
633 case FieldType.SFixed64:
634 case FieldType.Int32:
635 case FieldType.Int64:
636 case FieldType.SInt32:
637 case FieldType.SInt64:
638 case FieldType.UInt32:
639 case FieldType.UInt64:
640 keyText = ((IFormattable) pair.Key).ToString("d", Cultur eInfo.InvariantCulture);
641 break;
642 default:
643 throw new ArgumentException("Invalid key type: " + keyTy pe.FieldType);
644 }
645 WriteString(builder, keyText);
646 builder.Append(": ");
647 WriteSingleValue(builder, valueType, pair.Value);
648 first = false;
649 }
650 builder.Append(first ? "}" : " }");
651 }
652
653 /// <summary>
654 /// Returns whether or not a singular value can be represented in JSON.
655 /// Currently only relevant for enums, where unknown values can't be rep resented.
656 /// For repeated/map fields, this always returns true.
657 /// </summary>
658 private bool CanWriteSingleValue(FieldDescriptor descriptor, object valu e)
659 {
660 if (descriptor.FieldType == FieldType.Enum)
661 {
662 EnumValueDescriptor enumValue = descriptor.EnumType.FindValueByN umber((int) value);
663 return enumValue != null;
664 }
665 return true;
666 }
667
668 /// <summary>
669 /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
670 /// </summary>
671 /// <remarks>
672 /// Other than surrogate pair handling, this code is mostly taken from s rc/google/protobuf/util/internal/json_escaping.cc.
673 /// </remarks>
674 private void WriteString(StringBuilder builder, string text)
675 {
676 builder.Append('"');
677 AppendEscapedString(builder, text);
678 builder.Append('"');
679 }
680
681 /// <summary>
682 /// Appends the given text to the string builder, escaping as required.
683 /// </summary>
684 private void AppendEscapedString(StringBuilder builder, string text)
685 {
686 for (int i = 0; i < text.Length; i++)
687 {
688 char c = text[i];
689 if (c < 0xa0)
690 {
691 builder.Append(CommonRepresentations[c]);
692 continue;
693 }
694 if (char.IsHighSurrogate(c))
695 {
696 // Encountered first part of a surrogate pair.
697 // Check that we have the whole pair, and encode both parts as hex.
698 i++;
699 if (i == text.Length || !char.IsLowSurrogate(text[i]))
700 {
701 throw new ArgumentException("String contains low surroga te not followed by high surrogate");
702 }
703 HexEncodeUtf16CodeUnit(builder, c);
704 HexEncodeUtf16CodeUnit(builder, text[i]);
705 continue;
706 }
707 else if (char.IsLowSurrogate(c))
708 {
709 throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
710 }
711 switch ((uint) c)
712 {
713 // These are not required by json spec
714 // but used to prevent security bugs in javascript.
715 case 0xfeff: // Zero width no-break space
716 case 0xfff9: // Interlinear annotation anchor
717 case 0xfffa: // Interlinear annotation separator
718 case 0xfffb: // Interlinear annotation terminator
719
720 case 0x00ad: // Soft-hyphen
721 case 0x06dd: // Arabic end of ayah
722 case 0x070f: // Syriac abbreviation mark
723 case 0x17b4: // Khmer vowel inherent Aq
724 case 0x17b5: // Khmer vowel inherent Aa
725 HexEncodeUtf16CodeUnit(builder, c);
726 break;
727
728 default:
729 if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs
730 (c >= 0x200b && c <= 0x200f) || // Zero width etc.
731 (c >= 0x2028 && c <= 0x202e) || // Separators etc.
732 (c >= 0x2060 && c <= 0x2064) || // Invisible etc.
733 (c >= 0x206a && c <= 0x206f))
734 {
735 HexEncodeUtf16CodeUnit(builder, c);
736 }
737 else
738 {
739 // No handling of surrogates here - that's done earl ier
740 builder.Append(c);
741 }
742 break;
743 }
744 }
745 }
746
747 private const string Hex = "0123456789abcdef";
748 private static void HexEncodeUtf16CodeUnit(StringBuilder builder, char c )
749 {
750 builder.Append("\\u");
751 builder.Append(Hex[(c >> 12) & 0xf]);
752 builder.Append(Hex[(c >> 8) & 0xf]);
753 builder.Append(Hex[(c >> 4) & 0xf]);
754 builder.Append(Hex[(c >> 0) & 0xf]);
755 }
756
757 /// <summary>
758 /// Settings controlling JSON formatting.
759 /// </summary>
760 public sealed class Settings
761 {
762 private static readonly Settings defaultInstance = new Settings(fals e);
763
764 /// <summary>
765 /// Default settings, as used by <see cref="JsonFormatter.Default"/>
766 /// </summary>
767 public static Settings Default { get { return defaultInstance; } }
768
769 private readonly bool formatDefaultValues;
770
771
772 /// <summary>
773 /// Whether fields whose values are the default for the field type ( e.g. 0 for integers)
774 /// should be formatted (true) or omitted (false).
775 /// </summary>
776 public bool FormatDefaultValues { get { return formatDefaultValues; } }
777
778 /// <summary>
779 /// Creates a new <see cref="Settings"/> object with the specified f ormatting of default values.
780 /// </summary>
781 /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
782 public Settings(bool formatDefaultValues)
783 {
784 this.formatDefaultValues = formatDefaultValues;
785 }
786 }
787 }
788 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698