JsonFormatter.cs 39.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

using System;
using System.Collections;
using System.Globalization;
using System.Text;
37
using Google.Protobuf.Reflection;
38
using Google.Protobuf.WellKnownTypes;
39
using System.IO;
40
using System.Linq;
41
using System.Collections.Generic;
42
using System.Reflection;
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

namespace Google.Protobuf
{
    /// <summary>
    /// Reflection-based converter from messages to JSON.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Instances of this class are thread-safe, with no mutable state.
    /// </para>
    /// <para>
    /// This is a simple start to get JSON formatting working. As it's reflection-based,
    /// it's not as quick as baking calls into generated messages - but is a simpler implementation.
    /// (This code is generally not heavily optimized.)
    /// </para>
    /// </remarks>
    public sealed class JsonFormatter
    {
Jon Skeet's avatar
Jon Skeet committed
61
        internal const string AnyTypeUrlField = "@type";
62
        internal const string AnyDiagnosticValueField = "@value";
Jon Skeet's avatar
Jon Skeet committed
63 64 65 66 67
        internal const string AnyWellKnownTypeValueField = "value";
        private const string TypeUrlPrefix = "type.googleapis.com";
        private const string NameValueSeparator = ": ";
        private const string PropertySeparator = ", ";

68 69 70
        /// <summary>
        /// Returns a formatter using the default settings.
        /// </summary>
71 72
        public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default);

73
        // A JSON formatter which *only* exists
74
        private static readonly JsonFormatter diagnosticFormatter = new JsonFormatter(Settings.Default);
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

        /// <summary>
        /// The JSON representation of the first 160 characters of Unicode.
        /// Empty strings are replaced by the static constructor.
        /// </summary>
        private static readonly string[] CommonRepresentations = {
            // C0 (ASCII and derivatives) control characters
            "\\u0000", "\\u0001", "\\u0002", "\\u0003",  // 0x00
          "\\u0004", "\\u0005", "\\u0006", "\\u0007",
          "\\b",     "\\t",     "\\n",     "\\u000b",
          "\\f",     "\\r",     "\\u000e", "\\u000f",
          "\\u0010", "\\u0011", "\\u0012", "\\u0013",  // 0x10
          "\\u0014", "\\u0015", "\\u0016", "\\u0017",
          "\\u0018", "\\u0019", "\\u001a", "\\u001b",
          "\\u001c", "\\u001d", "\\u001e", "\\u001f",
            // Escaping of " and \ are required by www.json.org string definition.
            // Escaping of < and > are required for HTML security.
            "", "", "\\\"", "", "",        "", "",        "",  // 0x20
          "", "", "",     "", "",        "", "",        "",
          "", "", "",     "", "",        "", "",        "",  // 0x30
          "", "", "",     "", "\\u003c", "", "\\u003e", "",
          "", "", "",     "", "",        "", "",        "",  // 0x40
          "", "", "",     "", "",        "", "",        "",
          "", "", "",     "", "",        "", "",        "",  // 0x50
          "", "", "",     "", "\\\\",    "", "",        "",
          "", "", "",     "", "",        "", "",        "",  // 0x60
          "", "", "",     "", "",        "", "",        "",
          "", "", "",     "", "",        "", "",        "",  // 0x70
          "", "", "",     "", "",        "", "",        "\\u007f",
            // C1 (ISO 8859 and Unicode) extended control characters
            "\\u0080", "\\u0081", "\\u0082", "\\u0083",  // 0x80
          "\\u0084", "\\u0085", "\\u0086", "\\u0087",
          "\\u0088", "\\u0089", "\\u008a", "\\u008b",
          "\\u008c", "\\u008d", "\\u008e", "\\u008f",
          "\\u0090", "\\u0091", "\\u0092", "\\u0093",  // 0x90
          "\\u0094", "\\u0095", "\\u0096", "\\u0097",
          "\\u0098", "\\u0099", "\\u009a", "\\u009b",
          "\\u009c", "\\u009d", "\\u009e", "\\u009f"
        };

        static JsonFormatter()
        {
            for (int i = 0; i < CommonRepresentations.Length; i++)
            {
                if (CommonRepresentations[i] == "")
                {
                    CommonRepresentations[i] = ((char) i).ToString();
                }
            }
        }

        private readonly Settings settings;

128 129
        private bool DiagnosticOnly => ReferenceEquals(this, diagnosticFormatter);

130 131 132 133
        /// <summary>
        /// Creates a new formatted with the given settings.
        /// </summary>
        /// <param name="settings">The settings.</param>
134 135 136 137 138
        public JsonFormatter(Settings settings)
        {
            this.settings = settings;
        }

139 140 141 142 143
        /// <summary>
        /// Formats the specified message as JSON.
        /// </summary>
        /// <param name="message">The message to format.</param>
        /// <returns>The formatted message.</returns>
Jon Skeet's avatar
Jon Skeet committed
144
        public string Format(IMessage message)
145 146 147 148 149 150 151 152 153 154 155 156 157
        {
            var writer = new StringWriter();
            Format(message, writer);
            return writer.ToString();
        }

        /// <summary>
        /// Formats the specified message as JSON.
        /// </summary>
        /// <param name="message">The message to format.</param>
        /// <param name="writer">The TextWriter to write the formatted message to.</param>
        /// <returns>The formatted message.</returns>
        public void Format(IMessage message, TextWriter writer)
158
        {
159
            ProtoPreconditions.CheckNotNull(message, nameof(message));
160 161
            ProtoPreconditions.CheckNotNull(writer, nameof(writer));

162 163
            if (message.Descriptor.IsWellKnownType)
            {
164
                WriteWellKnownTypeValue(writer, message.Descriptor, message);
165 166 167
            }
            else
            {
168
                WriteMessage(writer, message);
169
            }
170 171
        }

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
        /// <summary>
        /// Converts a message to JSON for diagnostic purposes with no extra context.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This differs from calling <see cref="Format(IMessage)"/> on the default JSON
        /// formatter in its handling of <see cref="Any"/>. As no type registry is available
        /// in <see cref="object.ToString"/> calls, the normal way of resolving the type of
        /// an <c>Any</c> message cannot be applied. Instead, a JSON property named <c>@value</c>
        /// is included with the base64 data from the <see cref="Any.Value"/> property of the message.
        /// </para>
        /// <para>The value returned by this method is only designed to be used for diagnostic
        /// purposes. It may not be parsable by <see cref="JsonParser"/>, and may not be parsable
        /// by other Protocol Buffer implementations.</para>
        /// </remarks>
        /// <param name="message">The message to format for diagnostic purposes.</param>
        /// <returns>The diagnostic-only JSON representation of the message</returns>
        public static string ToDiagnosticString(IMessage message)
        {
191
            ProtoPreconditions.CheckNotNull(message, nameof(message));
192 193 194
            return diagnosticFormatter.Format(message);
        }

195
        private void WriteMessage(TextWriter writer, IMessage message)
196 197 198
        {
            if (message == null)
            {
199
                WriteNull(writer);
200 201
                return;
            }
202
            if (DiagnosticOnly)
203 204 205 206
            {
                ICustomDiagnosticMessage customDiagnosticMessage = message as ICustomDiagnosticMessage;
                if (customDiagnosticMessage != null)
                {
207
                    writer.Write(customDiagnosticMessage.ToDiagnosticString());
208 209 210
                    return;
                }
            }
211 212 213
            writer.Write("{ ");
            bool writtenFields = WriteMessageFields(writer, message, false);
            writer.Write(writtenFields ? " }" : "}");
Jon Skeet's avatar
Jon Skeet committed
214 215
        }

216
        private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten)
Jon Skeet's avatar
Jon Skeet committed
217
        {
Jon Skeet's avatar
Jon Skeet committed
218
            var fields = message.Descriptor.Fields;
Jon Skeet's avatar
Jon Skeet committed
219
            bool first = !assumeFirstFieldWritten;
Jon Skeet's avatar
Jon Skeet committed
220
            // First non-oneof fields
221
            foreach (var field in fields.InFieldNumberOrder())
222
            {
Jon Skeet's avatar
Jon Skeet committed
223
                var accessor = field.Accessor;
224
                if (field.ContainingOneof != null && field.ContainingOneof.Accessor.GetCaseFieldDescriptor(message) != field)
Jon Skeet's avatar
Jon Skeet committed
225 226 227
                {
                    continue;
                }
228 229
                // Omit default values unless we're asked to format them, or they're oneofs (where the default
                // value is still formatted regardless, because that's how we preserve the oneof case).
230
                object value = accessor.GetValue(message);
231
                if (field.ContainingOneof == null && !settings.FormatDefaultValues && IsDefaultValue(accessor, value))
232 233 234
                {
                    continue;
                }
Jon Skeet's avatar
Jon Skeet committed
235 236

                // Okay, all tests complete: let's write the field value...
237 238
                if (!first)
                {
239
                    writer.Write(PropertySeparator);
240
                }
241

alien's avatar
alien committed
242
                WriteString(writer, accessor.Descriptor.JsonName);
243 244
                writer.Write(NameValueSeparator);
                WriteValue(writer, value);
245

246
                first = false;
247
            }
Jon Skeet's avatar
Jon Skeet committed
248
            return !first;
249 250
        }

251 252
        // Converted from java/core/src/main/java/com/google/protobuf/Descriptors.java
        internal static string ToJsonName(string name)
253
        {
254 255 256
            StringBuilder result = new StringBuilder(name.Length);
            bool isNextUpperCase = false;
            foreach (char ch in name)
257
            {
258
                if (ch == '_')
259
                {
260
                    isNextUpperCase = true;
261
                }
262
                else if (isNextUpperCase)
263
                {
264 265
                    result.Append(char.ToUpperInvariant(ch));
                    isNextUpperCase = false;
266 267 268
                }
                else
                {
269
                    result.Append(ch);
270 271 272 273
                }
            }
            return result.ToString();
        }
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292

        internal static string FromJsonName(string name)
        {
            StringBuilder result = new StringBuilder(name.Length);
            foreach (char ch in name)
            {
                if (char.IsUpper(ch))
                {
                    result.Append('_');
                    result.Append(char.ToLowerInvariant(ch));
                }
                else
                {
                    result.Append(ch);
                }
            }
            return result.ToString();
        }

293
        private static void WriteNull(TextWriter writer)
294
        {
295
            writer.Write("null");
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
        }

        private static bool IsDefaultValue(IFieldAccessor accessor, object value)
        {
            if (accessor.Descriptor.IsMap)
            {
                IDictionary dictionary = (IDictionary) value;
                return dictionary.Count == 0;
            }
            if (accessor.Descriptor.IsRepeated)
            {
                IList list = (IList) value;
                return list.Count == 0;
            }
            switch (accessor.Descriptor.FieldType)
            {
                case FieldType.Bool:
                    return (bool) value == false;
                case FieldType.Bytes:
                    return (ByteString) value == ByteString.Empty;
                case FieldType.String:
                    return (string) value == "";
                case FieldType.Double:
                    return (double) value == 0.0;
                case FieldType.SInt32:
                case FieldType.Int32:
                case FieldType.SFixed32:
                case FieldType.Enum:
                    return (int) value == 0;
                case FieldType.Fixed32:
                case FieldType.UInt32:
                    return (uint) value == 0;
                case FieldType.Fixed64:
                case FieldType.UInt64:
                    return (ulong) value == 0;
                case FieldType.SFixed64:
                case FieldType.Int64:
                case FieldType.SInt64:
                    return (long) value == 0;
                case FieldType.Float:
                    return (float) value == 0f;
                case FieldType.Message:
                case FieldType.Group: // Never expect to get this, but...
                    return value == null;
                default:
                    throw new ArgumentException("Invalid field type");
            }
        }
344 345 346 347 348 349 350 351 352 353

        /// <summary>
        /// Writes a single value to the given writer as JSON. Only types understood by
        /// Protocol Buffers can be written in this way. This method is only exposed for
        /// advanced use cases; most users should be using <see cref="Format(IMessage)"/>
        /// or <see cref="Format(IMessage, TextWriter)"/>.
        /// </summary>
        /// <param name="writer">The writer to write the value to. Must not be null.</param>
        /// <param name="value">The value to write. May be null.</param>
        public void WriteValue(TextWriter writer, object value)
354
        {
355
            if (value == null)
356
            {
357
                WriteNull(writer);
358
            }
359
            else if (value is bool)
360
            {
361
                writer.Write((bool)value ? "true" : "false");
362
            }
363
            else if (value is ByteString)
364
            {
365
                // Nothing in Base64 needs escaping
366 367 368
                writer.Write('"');
                writer.Write(((ByteString)value).ToBase64());
                writer.Write('"');
369
            }
370
            else if (value is string)
371
            {
372
                WriteString(writer, (string)value);
373 374 375
            }
            else if (value is IDictionary)
            {
376
                WriteDictionary(writer, (IDictionary)value);
377 378 379
            }
            else if (value is IList)
            {
380
                WriteList(writer, (IList)value);
381 382 383 384
            }
            else if (value is int || value is uint)
            {
                IFormattable formattable = (IFormattable) value;
385
                writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
386 387 388
            }
            else if (value is long || value is ulong)
            {
389
                writer.Write('"');
390
                IFormattable formattable = (IFormattable) value;
391 392
                writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
                writer.Write('"');
393 394 395
            }
            else if (value is System.Enum)
            {
396
                if (settings.FormatEnumsAsIntegers)
397
                {
398
                    WriteValue(writer, (int)value);
399 400 401
                }
                else
                {
402 403 404 405 406 407 408 409 410
                    string name = OriginalEnumValueHelper.GetOriginalName(value);
                    if (name != null)
                    {
                        WriteString(writer, name);
                    }
                    else
                    {
                        WriteValue(writer, (int)value);
                    }
411
                }
412 413 414 415 416 417
            }
            else if (value is float || value is double)
            {
                string text = ((IFormattable) value).ToString("r", CultureInfo.InvariantCulture);
                if (text == "NaN" || text == "Infinity" || text == "-Infinity")
                {
418 419 420
                    writer.Write('"');
                    writer.Write(text);
                    writer.Write('"');
421 422 423
                }
                else
                {
424
                    writer.Write(text);
425 426 427 428
                }
            }
            else if (value is IMessage)
            {
429
                Format((IMessage)value, writer);
430 431 432 433
            }
            else
            {
                throw new ArgumentException("Unable to format value of type " + value.GetType());
434 435 436
            }
        }

437 438
        /// <summary>
        /// Central interception point for well-known type formatting. Any well-known types which
439 440 441
        /// don't need special handling can fall back to WriteMessage. We avoid assuming that the
        /// values are using the embedded well-known types, in order to allow for dynamic messages
        /// in the future.
442
        /// </summary>
443
        private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value)
444
        {
Jon Skeet's avatar
Jon Skeet committed
445 446
            // Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*,
            // this would do the right thing.
447 448
            if (value == null)
            {
449
                WriteNull(writer);
450 451
                return;
            }
452 453 454 455 456
            // For wrapper types, the value will either be the (possibly boxed) "native" value,
            // or the message itself if we're formatting it at the top level (e.g. just calling ToString on the object itself).
            // If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value,
            // and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string...
            // WriteValue will do the right thing.)
457
            if (descriptor.IsWrapperType)
458
            {
459 460 461
                if (value is IMessage)
                {
                    var message = (IMessage) value;
462
                    value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message);
463
                }
464
                WriteValue(writer, value);
465 466
                return;
            }
467
            if (descriptor.FullName == Timestamp.Descriptor.FullName)
468
            {
469
                WriteTimestamp(writer, (IMessage)value);
470 471
                return;
            }
472
            if (descriptor.FullName == Duration.Descriptor.FullName)
473
            {
474
                WriteDuration(writer, (IMessage)value);
475 476
                return;
            }
477 478
            if (descriptor.FullName == FieldMask.Descriptor.FullName)
            {
479
                WriteFieldMask(writer, (IMessage)value);
480 481
                return;
            }
482 483
            if (descriptor.FullName == Struct.Descriptor.FullName)
            {
484
                WriteStruct(writer, (IMessage)value);
485 486 487 488 489
                return;
            }
            if (descriptor.FullName == ListValue.Descriptor.FullName)
            {
                var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
490
                WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value));
491 492 493 494
                return;
            }
            if (descriptor.FullName == Value.Descriptor.FullName)
            {
495
                WriteStructFieldValue(writer, (IMessage)value);
496 497
                return;
            }
Jon Skeet's avatar
Jon Skeet committed
498 499
            if (descriptor.FullName == Any.Descriptor.FullName)
            {
500
                WriteAny(writer, (IMessage)value);
Jon Skeet's avatar
Jon Skeet committed
501 502
                return;
            }
503
            WriteMessage(writer, (IMessage)value);
504 505
        }

506
        private void WriteTimestamp(TextWriter writer, IMessage value)
507 508 509 510 511 512 513
        {
            // TODO: In the common case where this *is* using the built-in Timestamp type, we could
            // avoid all the reflection at this point, by casting to Timestamp. In the interests of
            // avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
            // it still works in that case.
            int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value);
            long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value);
514
            writer.Write(Timestamp.ToJson(seconds, nanos, DiagnosticOnly));
515 516
        }

517
        private void WriteDuration(TextWriter writer, IMessage value)
518
        {
519
            // TODO: Same as for WriteTimestamp
520 521
            int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
            long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
522
            writer.Write(Duration.ToJson(seconds, nanos, DiagnosticOnly));
523 524
        }

525
        private void WriteFieldMask(TextWriter writer, IMessage value)
526
        {
527
            var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value);
528
            writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
529 530
        }

531
        private void WriteAny(TextWriter writer, IMessage value)
Jon Skeet's avatar
Jon Skeet committed
532
        {
533
            if (DiagnosticOnly)
534
            {
535
                WriteDiagnosticOnlyAny(writer, value);
536 537 538
                return;
            }

Jon Skeet's avatar
Jon Skeet committed
539 540
            string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
            ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
541
            string typeName = Any.GetTypeName(typeUrl);
Jon Skeet's avatar
Jon Skeet committed
542 543 544 545 546 547
            MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
            if (descriptor == null)
            {
                throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
            }
            IMessage message = descriptor.Parser.ParseFrom(data);
548 549 550 551
            writer.Write("{ ");
            WriteString(writer, AnyTypeUrlField);
            writer.Write(NameValueSeparator);
            WriteString(writer, typeUrl);
Jon Skeet's avatar
Jon Skeet committed
552 553 554

            if (descriptor.IsWellKnownType)
            {
555 556 557 558
                writer.Write(PropertySeparator);
                WriteString(writer, AnyWellKnownTypeValueField);
                writer.Write(NameValueSeparator);
                WriteWellKnownTypeValue(writer, descriptor, message);
Jon Skeet's avatar
Jon Skeet committed
559 560 561
            }
            else
            {
562
                WriteMessageFields(writer, message, true);
Jon Skeet's avatar
Jon Skeet committed
563
            }
564
            writer.Write(" }");
Jon Skeet's avatar
Jon Skeet committed
565 566
        }

567
        private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
568 569 570
        {
            string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
            ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
571 572 573 574 575 576 577 578 579 580 581
            writer.Write("{ ");
            WriteString(writer, AnyTypeUrlField);
            writer.Write(NameValueSeparator);
            WriteString(writer, typeUrl);
            writer.Write(PropertySeparator);
            WriteString(writer, AnyDiagnosticValueField);
            writer.Write(NameValueSeparator);
            writer.Write('"');
            writer.Write(data.ToBase64());
            writer.Write('"');
            writer.Write(" }");
582
        }
Jon Skeet's avatar
Jon Skeet committed
583

584
        private void WriteStruct(TextWriter writer, IMessage message)
585
        {
586
            writer.Write("{ ");
587 588 589 590 591 592 593 594 595 596 597 598 599
            IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message);
            bool first = true;
            foreach (DictionaryEntry entry in fields)
            {
                string key = (string) entry.Key;
                IMessage value = (IMessage) entry.Value;
                if (string.IsNullOrEmpty(key) || value == null)
                {
                    throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
                }

                if (!first)
                {
600
                    writer.Write(PropertySeparator);
601
                }
602 603 604
                WriteString(writer, key);
                writer.Write(NameValueSeparator);
                WriteStructFieldValue(writer, value);
605 606
                first = false;
            }
607
            writer.Write(first ? "}" : " }");
608 609
        }

610
        private void WriteStructFieldValue(TextWriter writer, IMessage message)
611 612 613 614 615 616 617 618
        {
            var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message);
            if (specifiedField == null)
            {
                throw new InvalidOperationException("Value message must contain a value for the oneof.");
            }

            object value = specifiedField.Accessor.GetValue(message);
619

620 621 622 623 624
            switch (specifiedField.FieldNumber)
            {
                case Value.BoolValueFieldNumber:
                case Value.StringValueFieldNumber:
                case Value.NumberValueFieldNumber:
625
                    WriteValue(writer, value);
626 627 628 629 630
                    return;
                case Value.StructValueFieldNumber:
                case Value.ListValueFieldNumber:
                    // Structs and ListValues are nested messages, and already well-known types.
                    var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message);
631
                    WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage);
632 633
                    return;
                case Value.NullValueFieldNumber:
634
                    WriteNull(writer);
635 636 637 638 639 640
                    return;
                default:
                    throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber);
            }
        }

641
        internal void WriteList(TextWriter writer, IList list)
642
        {
643
            writer.Write("[ ");
644 645 646 647 648
            bool first = true;
            foreach (var value in list)
            {
                if (!first)
                {
649
                    writer.Write(PropertySeparator);
650
                }
651
                WriteValue(writer, value);
652 653
                first = false;
            }
654
            writer.Write(first ? "]" : " ]");
655 656
        }

657
        internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
658
        {
659
            writer.Write("{ ");
660 661 662 663 664 665
            bool first = true;
            // This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
            foreach (DictionaryEntry pair in dictionary)
            {
                if (!first)
                {
666
                    writer.Write(PropertySeparator);
667 668
                }
                string keyText;
669 670 671 672 673 674 675 676 677
                if (pair.Key is string)
                {
                    keyText = (string) pair.Key;
                }
                else if (pair.Key is bool)
                {
                    keyText = (bool) pair.Key ? "true" : "false";
                }
                else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
678
                {
679 680 681 682 683 684 685 686 687
                    keyText = ((IFormattable) pair.Key).ToString("d", CultureInfo.InvariantCulture);
                }
                else
                {
                    if (pair.Key == null)
                    {
                        throw new ArgumentException("Dictionary has entry with null key");
                    }
                    throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
688
                }
689 690 691
                WriteString(writer, keyText);
                writer.Write(NameValueSeparator);
                WriteValue(writer, pair.Value);
692 693
                first = false;
            }
694
            writer.Write(first ? "}" : " }");
695 696 697 698 699 700 701 702
        }

        /// <summary>
        /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
        /// </summary>
        /// <remarks>
        /// Other than surrogate pair handling, this code is mostly taken from src/google/protobuf/util/internal/json_escaping.cc.
        /// </remarks>
703
        internal static void WriteString(TextWriter writer, string text)
704
        {
705
            writer.Write('"');
706 707 708 709 710
            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];
                if (c < 0xa0)
                {
711
                    writer.Write(CommonRepresentations[c]);
712 713 714 715 716 717 718 719 720 721 722
                    continue;
                }
                if (char.IsHighSurrogate(c))
                {
                    // Encountered first part of a surrogate pair.
                    // Check that we have the whole pair, and encode both parts as hex.
                    i++;
                    if (i == text.Length || !char.IsLowSurrogate(text[i]))
                    {
                        throw new ArgumentException("String contains low surrogate not followed by high surrogate");
                    }
723 724
                    HexEncodeUtf16CodeUnit(writer, c);
                    HexEncodeUtf16CodeUnit(writer, text[i]);
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
                    continue;
                }
                else if (char.IsLowSurrogate(c))
                {
                    throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
                }
                switch ((uint) c)
                {
                    // These are not required by json spec
                    // but used to prevent security bugs in javascript.
                    case 0xfeff:  // Zero width no-break space
                    case 0xfff9:  // Interlinear annotation anchor
                    case 0xfffa:  // Interlinear annotation separator
                    case 0xfffb:  // Interlinear annotation terminator

                    case 0x00ad:  // Soft-hyphen
                    case 0x06dd:  // Arabic end of ayah
                    case 0x070f:  // Syriac abbreviation mark
                    case 0x17b4:  // Khmer vowel inherent Aq
                    case 0x17b5:  // Khmer vowel inherent Aa
745
                        HexEncodeUtf16CodeUnit(writer, c);
746 747 748 749 750 751 752 753 754
                        break;

                    default:
                        if ((c >= 0x0600 && c <= 0x0603) ||  // Arabic signs
                            (c >= 0x200b && c <= 0x200f) ||  // Zero width etc.
                            (c >= 0x2028 && c <= 0x202e) ||  // Separators etc.
                            (c >= 0x2060 && c <= 0x2064) ||  // Invisible etc.
                            (c >= 0x206a && c <= 0x206f))
                        {
755
                            HexEncodeUtf16CodeUnit(writer, c);
756 757 758 759
                        }
                        else
                        {
                            // No handling of surrogates here - that's done earlier
760
                            writer.Write(c);
761 762 763 764
                        }
                        break;
                }
            }
765
            writer.Write('"');
766
        }
767 768

        private const string Hex = "0123456789abcdef";
769
        private static void HexEncodeUtf16CodeUnit(TextWriter writer, char c)
770
        {
771 772 773 774 775
            writer.Write("\\u");
            writer.Write(Hex[(c >> 12) & 0xf]);
            writer.Write(Hex[(c >> 8) & 0xf]);
            writer.Write(Hex[(c >> 4) & 0xf]);
            writer.Write(Hex[(c >> 0) & 0xf]);
776 777 778 779 780 781 782 783 784 785
        }

        /// <summary>
        /// Settings controlling JSON formatting.
        /// </summary>
        public sealed class Settings
        {
            /// <summary>
            /// Default settings, as used by <see cref="JsonFormatter.Default"/>
            /// </summary>
786 787 788 789 790 791 792 793
            public static Settings Default { get; }

            // Workaround for the Mono compiler complaining about XML comments not being on
            // valid language elements.
            static Settings()
            {
                Default = new Settings(false);
            }
794 795 796 797 798

            /// <summary>
            /// Whether fields whose values are the default for the field type (e.g. 0 for integers)
            /// should be formatted (true) or omitted (false).
            /// </summary>
Jon Skeet's avatar
Jon Skeet committed
799 800 801 802 803 804 805
            public bool FormatDefaultValues { get; }

            /// <summary>
            /// The type registry used to format <see cref="Any"/> messages.
            /// </summary>
            public TypeRegistry TypeRegistry { get; }

806 807 808 809 810
            /// <summary>
            /// Whether to format enums as ints. Defaults to false.
            /// </summary>
            public bool FormatEnumsAsIntegers { get; }

Jon Skeet's avatar
Jon Skeet committed
811 812 813 814 815 816 817 818 819

            /// <summary>
            /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
            /// and an empty type registry.
            /// </summary>
            /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
            public Settings(bool formatDefaultValues) : this(formatDefaultValues, TypeRegistry.Empty)
            {
            }
820

821
            /// <summary>
Jon Skeet's avatar
Jon Skeet committed
822 823
            /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
            /// and type registry.
824 825
            /// </summary>
            /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
Jon Skeet's avatar
Jon Skeet committed
826
            /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
827 828 829 830 831 832 833 834 835 836 837 838 839
            public Settings(bool formatDefaultValues, TypeRegistry typeRegistry) : this(formatDefaultValues, typeRegistry, false)
            {
            }

            /// <summary>
            /// Creates a new <see cref="Settings"/> object with the specified parameters.
            /// </summary>
            /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
            /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages. TypeRegistry.Empty will be used if it is null.</param>
            /// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
            private Settings(bool formatDefaultValues,
                            TypeRegistry typeRegistry,
                            bool formatEnumsAsIntegers)
840
            {
Jon Skeet's avatar
Jon Skeet committed
841
                FormatDefaultValues = formatDefaultValues;
842 843
                TypeRegistry = typeRegistry ?? TypeRegistry.Empty;
                FormatEnumsAsIntegers = formatEnumsAsIntegers;
844
            }
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862

            /// <summary>
            /// Creates a new <see cref="Settings"/> object with the specified formatting of default values and the current settings.
            /// </summary>
            /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
            public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers);

            /// <summary>
            /// Creates a new <see cref="Settings"/> object with the specified type registry and the current settings.
            /// </summary>
            /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
            public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers);

            /// <summary>
            /// Creates a new <see cref="Settings"/> object with the specified enums formatting option and the current settings.
            /// </summary>
            /// <param name="formatEnumsAsIntegers"><c>true</c> to format the enums as integers; <c>false</c> to format enums as enum names.</param>
            public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers);
863
        }
864 865 866 867 868 869 870 871 872 873

        // Effectively a cache of mapping from enum values to the original name as specified in the proto file,
        // fetched by reflection.
        // The need for this is unfortunate, as is its unbounded size, but realistically it shouldn't cause issues.
        private static class OriginalEnumValueHelper
        {
            // TODO: In the future we might want to use ConcurrentDictionary, at the point where all
            // the platforms we target have it.
            private static readonly Dictionary<System.Type, Dictionary<object, string>> dictionaries
                = new Dictionary<System.Type, Dictionary<object, string>>();
874

875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
            internal static string GetOriginalName(object value)
            {
                var enumType = value.GetType();
                Dictionary<object, string> nameMapping;
                lock (dictionaries)
                {
                    if (!dictionaries.TryGetValue(enumType, out nameMapping))
                    {
                        nameMapping = GetNameMapping(enumType);
                        dictionaries[enumType] = nameMapping;
                    }
                }

                string originalName;
                // If this returns false, originalName will be null, which is what we want.
                nameMapping.TryGetValue(value, out originalName);
                return originalName;
            }

894
#if NET35
895 896 897
            // TODO: Consider adding functionality to TypeExtensions to avoid this difference.
            private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
                enumType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
898 899 900
                    .Where(f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false)
                                 .FirstOrDefault() as OriginalNameAttribute)
                                 ?.PreferredAlias ?? true)
901 902 903 904 905 906
                    .ToDictionary(f => f.GetValue(null),
                                  f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false)
                                        .FirstOrDefault() as OriginalNameAttribute)
                                        // If the attribute hasn't been applied, fall back to the name of the field.
                                        ?.Name ?? f.Name);
#else
907 908 909
            private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
                enumType.GetTypeInfo().DeclaredFields
                    .Where(f => f.IsStatic)
910 911
                    .Where(f => f.GetCustomAttributes<OriginalNameAttribute>()
                                 .FirstOrDefault()?.PreferredAlias ?? true)
912 913 914 915 916
                    .ToDictionary(f => f.GetValue(null),
                                  f => f.GetCustomAttributes<OriginalNameAttribute>()
                                        .FirstOrDefault()
                                        // If the attribute hasn't been applied, fall back to the name of the field.
                                        ?.Name ?? f.Name);
917
#endif
918
        }
919 920
    }
}