JsonFormatter.cs 38.9 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 73 74
        public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default);

        // A JSON formatter which *only* exists 
        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 274
                }
            }
            return result.ToString();
        }
        
275
        private static void WriteNull(TextWriter writer)
276
        {
277
            writer.Write("null");
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 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
        }

        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");
            }
        }
326 327 328 329 330 331 332 333 334 335

        /// <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)
336
        {
337
            if (value == null)
338
            {
339
                WriteNull(writer);
340
            }
341
            else if (value is bool)
342
            {
343
                writer.Write((bool)value ? "true" : "false");
344
            }
345
            else if (value is ByteString)
346
            {
347
                // Nothing in Base64 needs escaping
348 349 350
                writer.Write('"');
                writer.Write(((ByteString)value).ToBase64());
                writer.Write('"');
351
            }
352
            else if (value is string)
353
            {
354
                WriteString(writer, (string)value);
355 356 357
            }
            else if (value is IDictionary)
            {
358
                WriteDictionary(writer, (IDictionary)value);
359 360 361
            }
            else if (value is IList)
            {
362
                WriteList(writer, (IList)value);
363 364 365 366
            }
            else if (value is int || value is uint)
            {
                IFormattable formattable = (IFormattable) value;
367
                writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
368 369 370
            }
            else if (value is long || value is ulong)
            {
371
                writer.Write('"');
372
                IFormattable formattable = (IFormattable) value;
373 374
                writer.Write(formattable.ToString("d", CultureInfo.InvariantCulture));
                writer.Write('"');
375 376 377
            }
            else if (value is System.Enum)
            {
378
                if (settings.FormatEnumsAsIntegers)
379
                {
380
                    WriteValue(writer, (int)value);
381 382 383
                }
                else
                {
384 385 386 387 388 389 390 391 392
                    string name = OriginalEnumValueHelper.GetOriginalName(value);
                    if (name != null)
                    {
                        WriteString(writer, name);
                    }
                    else
                    {
                        WriteValue(writer, (int)value);
                    }
393
                }
394 395 396 397 398 399
            }
            else if (value is float || value is double)
            {
                string text = ((IFormattable) value).ToString("r", CultureInfo.InvariantCulture);
                if (text == "NaN" || text == "Infinity" || text == "-Infinity")
                {
400 401 402
                    writer.Write('"');
                    writer.Write(text);
                    writer.Write('"');
403 404 405
                }
                else
                {
406
                    writer.Write(text);
407 408 409 410
                }
            }
            else if (value is IMessage)
            {
411
                Format((IMessage)value, writer);
412 413 414 415
            }
            else
            {
                throw new ArgumentException("Unable to format value of type " + value.GetType());
416 417 418
            }
        }

419 420
        /// <summary>
        /// Central interception point for well-known type formatting. Any well-known types which
421 422 423
        /// 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.
424
        /// </summary>
425
        private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value)
426
        {
Jon Skeet's avatar
Jon Skeet committed
427 428
            // 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.
429 430
            if (value == null)
            {
431
                WriteNull(writer);
432 433
                return;
            }
434 435 436 437 438
            // 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.)
439
            if (descriptor.IsWrapperType)
440
            {
441 442 443
                if (value is IMessage)
                {
                    var message = (IMessage) value;
444
                    value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message);
445
                }
446
                WriteValue(writer, value);
447 448
                return;
            }
449
            if (descriptor.FullName == Timestamp.Descriptor.FullName)
450
            {
451
                WriteTimestamp(writer, (IMessage)value);
452 453
                return;
            }
454
            if (descriptor.FullName == Duration.Descriptor.FullName)
455
            {
456
                WriteDuration(writer, (IMessage)value);
457 458
                return;
            }
459 460
            if (descriptor.FullName == FieldMask.Descriptor.FullName)
            {
461
                WriteFieldMask(writer, (IMessage)value);
462 463
                return;
            }
464 465
            if (descriptor.FullName == Struct.Descriptor.FullName)
            {
466
                WriteStruct(writer, (IMessage)value);
467 468 469 470 471
                return;
            }
            if (descriptor.FullName == ListValue.Descriptor.FullName)
            {
                var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
472
                WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value));
473 474 475 476
                return;
            }
            if (descriptor.FullName == Value.Descriptor.FullName)
            {
477
                WriteStructFieldValue(writer, (IMessage)value);
478 479
                return;
            }
Jon Skeet's avatar
Jon Skeet committed
480 481
            if (descriptor.FullName == Any.Descriptor.FullName)
            {
482
                WriteAny(writer, (IMessage)value);
Jon Skeet's avatar
Jon Skeet committed
483 484
                return;
            }
485
            WriteMessage(writer, (IMessage)value);
486 487
        }

488
        private void WriteTimestamp(TextWriter writer, IMessage value)
489 490 491 492 493 494 495
        {
            // 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);
496
            writer.Write(Timestamp.ToJson(seconds, nanos, DiagnosticOnly));
497 498
        }

499
        private void WriteDuration(TextWriter writer, IMessage value)
500
        {
501
            // TODO: Same as for WriteTimestamp
502 503
            int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
            long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
504
            writer.Write(Duration.ToJson(seconds, nanos, DiagnosticOnly));
505 506
        }

507
        private void WriteFieldMask(TextWriter writer, IMessage value)
508
        {
509
            var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value);
510
            writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
511 512
        }

513
        private void WriteAny(TextWriter writer, IMessage value)
Jon Skeet's avatar
Jon Skeet committed
514
        {
515
            if (DiagnosticOnly)
516
            {
517
                WriteDiagnosticOnlyAny(writer, value);
518 519 520
                return;
            }

Jon Skeet's avatar
Jon Skeet committed
521 522
            string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
            ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
523
            string typeName = Any.GetTypeName(typeUrl);
Jon Skeet's avatar
Jon Skeet committed
524 525 526 527 528 529
            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);
530 531 532 533
            writer.Write("{ ");
            WriteString(writer, AnyTypeUrlField);
            writer.Write(NameValueSeparator);
            WriteString(writer, typeUrl);
Jon Skeet's avatar
Jon Skeet committed
534 535 536

            if (descriptor.IsWellKnownType)
            {
537 538 539 540
                writer.Write(PropertySeparator);
                WriteString(writer, AnyWellKnownTypeValueField);
                writer.Write(NameValueSeparator);
                WriteWellKnownTypeValue(writer, descriptor, message);
Jon Skeet's avatar
Jon Skeet committed
541 542 543
            }
            else
            {
544
                WriteMessageFields(writer, message, true);
Jon Skeet's avatar
Jon Skeet committed
545
            }
546
            writer.Write(" }");
Jon Skeet's avatar
Jon Skeet committed
547 548
        }

549
        private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
550 551 552
        {
            string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
            ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
553 554 555 556 557 558 559 560 561 562 563
            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(" }");
564
        }        
Jon Skeet's avatar
Jon Skeet committed
565

566
        private void WriteStruct(TextWriter writer, IMessage message)
567
        {
568
            writer.Write("{ ");
569 570 571 572 573 574 575 576 577 578 579 580 581
            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)
                {
582
                    writer.Write(PropertySeparator);
583
                }
584 585 586
                WriteString(writer, key);
                writer.Write(NameValueSeparator);
                WriteStructFieldValue(writer, value);
587 588
                first = false;
            }
589
            writer.Write(first ? "}" : " }");
590 591
        }

592
        private void WriteStructFieldValue(TextWriter writer, IMessage message)
593 594 595 596 597 598 599 600 601 602 603 604 605 606
        {
            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);
            
            switch (specifiedField.FieldNumber)
            {
                case Value.BoolValueFieldNumber:
                case Value.StringValueFieldNumber:
                case Value.NumberValueFieldNumber:
607
                    WriteValue(writer, value);
608 609 610 611 612
                    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);
613
                    WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage);
614 615
                    return;
                case Value.NullValueFieldNumber:
616
                    WriteNull(writer);
617 618 619 620 621 622
                    return;
                default:
                    throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber);
            }
        }

623
        internal void WriteList(TextWriter writer, IList list)
624
        {
625
            writer.Write("[ ");
626 627 628 629 630
            bool first = true;
            foreach (var value in list)
            {
                if (!first)
                {
631
                    writer.Write(PropertySeparator);
632
                }
633
                WriteValue(writer, value);
634 635
                first = false;
            }
636
            writer.Write(first ? "]" : " ]");
637 638
        }

639
        internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
640
        {
641
            writer.Write("{ ");
642 643 644 645 646 647
            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)
                {
648
                    writer.Write(PropertySeparator);
649 650
                }
                string keyText;
651 652 653 654 655 656 657 658 659
                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)
660
                {
661 662 663 664 665 666 667 668 669
                    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());
670
                }
671 672 673
                WriteString(writer, keyText);
                writer.Write(NameValueSeparator);
                WriteValue(writer, pair.Value);
674 675
                first = false;
            }
676
            writer.Write(first ? "}" : " }");
677 678 679 680 681 682 683 684
        }

        /// <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>
685
        internal static void WriteString(TextWriter writer, string text)
686
        {
687
            writer.Write('"');
688 689 690 691 692
            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];
                if (c < 0xa0)
                {
693
                    writer.Write(CommonRepresentations[c]);
694 695 696 697 698 699 700 701 702 703 704
                    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");
                    }
705 706
                    HexEncodeUtf16CodeUnit(writer, c);
                    HexEncodeUtf16CodeUnit(writer, text[i]);
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
                    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
727
                        HexEncodeUtf16CodeUnit(writer, c);
728 729 730 731 732 733 734 735 736
                        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))
                        {
737
                            HexEncodeUtf16CodeUnit(writer, c);
738 739 740 741
                        }
                        else
                        {
                            // No handling of surrogates here - that's done earlier
742
                            writer.Write(c);
743 744 745 746
                        }
                        break;
                }
            }
747
            writer.Write('"');
748
        }
749 750

        private const string Hex = "0123456789abcdef";
751
        private static void HexEncodeUtf16CodeUnit(TextWriter writer, char c)
752
        {
753 754 755 756 757
            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]);
758 759 760 761 762 763 764 765 766 767
        }

        /// <summary>
        /// Settings controlling JSON formatting.
        /// </summary>
        public sealed class Settings
        {
            /// <summary>
            /// Default settings, as used by <see cref="JsonFormatter.Default"/>
            /// </summary>
768 769 770 771 772 773 774 775
            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);
            }
776 777 778 779 780

            /// <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
781 782 783 784 785 786 787
            public bool FormatDefaultValues { get; }

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

788 789 790 791 792
            /// <summary>
            /// Whether to format enums as ints. Defaults to false.
            /// </summary>
            public bool FormatEnumsAsIntegers { get; }

Jon Skeet's avatar
Jon Skeet committed
793 794 795 796 797 798 799 800 801

            /// <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)
            {
            }
802

803
            /// <summary>
Jon Skeet's avatar
Jon Skeet committed
804 805
            /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
            /// and type registry.
806 807
            /// </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
808
            /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
809 810 811 812 813 814 815 816 817 818 819 820 821
            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)
822
            {
Jon Skeet's avatar
Jon Skeet committed
823
                FormatDefaultValues = formatDefaultValues;
824 825
                TypeRegistry = typeRegistry ?? TypeRegistry.Empty;
                FormatEnumsAsIntegers = formatEnumsAsIntegers;
826
            }
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844

            /// <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);
845
        }
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875

        // 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>>();
            
            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;
            }

876
#if NET35
877 878 879
            // 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)
880 881 882
                    .Where(f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false)
                                 .FirstOrDefault() as OriginalNameAttribute)
                                 ?.PreferredAlias ?? true)
883 884 885 886 887 888
                    .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
889 890 891
            private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
                enumType.GetTypeInfo().DeclaredFields
                    .Where(f => f.IsStatic)
892 893
                    .Where(f => f.GetCustomAttributes<OriginalNameAttribute>()
                                 .FirstOrDefault()?.PreferredAlias ?? true)
894 895 896 897 898
                    .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);
899
#endif
900
        }
901 902
    }
}