JsonFormatter.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  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. using System;
  33. using System.Collections;
  34. using System.Globalization;
  35. using System.Text;
  36. using Google.Protobuf.Reflection;
  37. using Google.Protobuf.WellKnownTypes;
  38. using System.Linq;
  39. using System.Collections.Generic;
  40. namespace Google.Protobuf
  41. {
  42. /// <summary>
  43. /// Reflection-based converter from messages to JSON.
  44. /// </summary>
  45. /// <remarks>
  46. /// <para>
  47. /// Instances of this class are thread-safe, with no mutable state.
  48. /// </para>
  49. /// <para>
  50. /// This is a simple start to get JSON formatting working. As it's reflection-based,
  51. /// it's not as quick as baking calls into generated messages - but is a simpler implementation.
  52. /// (This code is generally not heavily optimized.)
  53. /// </para>
  54. /// </remarks>
  55. public sealed class JsonFormatter
  56. {
  57. internal const string AnyTypeUrlField = "@type";
  58. internal const string AnyDiagnosticValueField = "@value";
  59. internal const string AnyWellKnownTypeValueField = "value";
  60. private const string TypeUrlPrefix = "type.googleapis.com";
  61. private const string NameValueSeparator = ": ";
  62. private const string PropertySeparator = ", ";
  63. /// <summary>
  64. /// Returns a formatter using the default settings.
  65. /// </summary>
  66. public static JsonFormatter Default { get; } = new JsonFormatter(Settings.Default);
  67. // A JSON formatter which *only* exists
  68. private static readonly JsonFormatter diagnosticFormatter = new JsonFormatter(Settings.Default);
  69. /// <summary>
  70. /// The JSON representation of the first 160 characters of Unicode.
  71. /// Empty strings are replaced by the static constructor.
  72. /// </summary>
  73. private static readonly string[] CommonRepresentations = {
  74. // C0 (ASCII and derivatives) control characters
  75. "\\u0000", "\\u0001", "\\u0002", "\\u0003", // 0x00
  76. "\\u0004", "\\u0005", "\\u0006", "\\u0007",
  77. "\\b", "\\t", "\\n", "\\u000b",
  78. "\\f", "\\r", "\\u000e", "\\u000f",
  79. "\\u0010", "\\u0011", "\\u0012", "\\u0013", // 0x10
  80. "\\u0014", "\\u0015", "\\u0016", "\\u0017",
  81. "\\u0018", "\\u0019", "\\u001a", "\\u001b",
  82. "\\u001c", "\\u001d", "\\u001e", "\\u001f",
  83. // Escaping of " and \ are required by www.json.org string definition.
  84. // Escaping of < and > are required for HTML security.
  85. "", "", "\\\"", "", "", "", "", "", // 0x20
  86. "", "", "", "", "", "", "", "",
  87. "", "", "", "", "", "", "", "", // 0x30
  88. "", "", "", "", "\\u003c", "", "\\u003e", "",
  89. "", "", "", "", "", "", "", "", // 0x40
  90. "", "", "", "", "", "", "", "",
  91. "", "", "", "", "", "", "", "", // 0x50
  92. "", "", "", "", "\\\\", "", "", "",
  93. "", "", "", "", "", "", "", "", // 0x60
  94. "", "", "", "", "", "", "", "",
  95. "", "", "", "", "", "", "", "", // 0x70
  96. "", "", "", "", "", "", "", "\\u007f",
  97. // C1 (ISO 8859 and Unicode) extended control characters
  98. "\\u0080", "\\u0081", "\\u0082", "\\u0083", // 0x80
  99. "\\u0084", "\\u0085", "\\u0086", "\\u0087",
  100. "\\u0088", "\\u0089", "\\u008a", "\\u008b",
  101. "\\u008c", "\\u008d", "\\u008e", "\\u008f",
  102. "\\u0090", "\\u0091", "\\u0092", "\\u0093", // 0x90
  103. "\\u0094", "\\u0095", "\\u0096", "\\u0097",
  104. "\\u0098", "\\u0099", "\\u009a", "\\u009b",
  105. "\\u009c", "\\u009d", "\\u009e", "\\u009f"
  106. };
  107. static JsonFormatter()
  108. {
  109. for (int i = 0; i < CommonRepresentations.Length; i++)
  110. {
  111. if (CommonRepresentations[i] == "")
  112. {
  113. CommonRepresentations[i] = ((char) i).ToString();
  114. }
  115. }
  116. }
  117. private readonly Settings settings;
  118. private bool DiagnosticOnly => ReferenceEquals(this, diagnosticFormatter);
  119. /// <summary>
  120. /// Creates a new formatted with the given settings.
  121. /// </summary>
  122. /// <param name="settings">The settings.</param>
  123. public JsonFormatter(Settings settings)
  124. {
  125. this.settings = settings;
  126. }
  127. /// <summary>
  128. /// Formats the specified message as JSON.
  129. /// </summary>
  130. /// <param name="message">The message to format.</param>
  131. /// <returns>The formatted message.</returns>
  132. public string Format(IMessage message)
  133. {
  134. Preconditions.CheckNotNull(message, nameof(message));
  135. StringBuilder builder = new StringBuilder();
  136. if (message.Descriptor.IsWellKnownType)
  137. {
  138. WriteWellKnownTypeValue(builder, message.Descriptor, message);
  139. }
  140. else
  141. {
  142. WriteMessage(builder, message);
  143. }
  144. return builder.ToString();
  145. }
  146. /// <summary>
  147. /// Converts a message to JSON for diagnostic purposes with no extra context.
  148. /// </summary>
  149. /// <remarks>
  150. /// <para>
  151. /// This differs from calling <see cref="Format(IMessage)"/> on the default JSON
  152. /// formatter in its handling of <see cref="Any"/>. As no type registry is available
  153. /// in <see cref="object.ToString"/> calls, the normal way of resolving the type of
  154. /// an <c>Any</c> message cannot be applied. Instead, a JSON property named <c>@value</c>
  155. /// is included with the base64 data from the <see cref="Any.Value"/> property of the message.
  156. /// </para>
  157. /// <para>The value returned by this method is only designed to be used for diagnostic
  158. /// purposes. It may not be parsable by <see cref="JsonParser"/>, and may not be parsable
  159. /// by other Protocol Buffer implementations.</para>
  160. /// </remarks>
  161. /// <param name="message">The message to format for diagnostic purposes.</param>
  162. /// <returns>The diagnostic-only JSON representation of the message</returns>
  163. public static string ToDiagnosticString(IMessage message)
  164. {
  165. Preconditions.CheckNotNull(message, nameof(message));
  166. return diagnosticFormatter.Format(message);
  167. }
  168. private void WriteMessage(StringBuilder builder, IMessage message)
  169. {
  170. if (message == null)
  171. {
  172. WriteNull(builder);
  173. return;
  174. }
  175. if (DiagnosticOnly)
  176. {
  177. ICustomDiagnosticMessage customDiagnosticMessage = message as ICustomDiagnosticMessage;
  178. if (customDiagnosticMessage != null)
  179. {
  180. builder.Append(customDiagnosticMessage.ToDiagnosticString());
  181. return;
  182. }
  183. }
  184. builder.Append("{ ");
  185. bool writtenFields = WriteMessageFields(builder, message, false);
  186. builder.Append(writtenFields ? " }" : "}");
  187. }
  188. private bool WriteMessageFields(StringBuilder builder, IMessage message, bool assumeFirstFieldWritten)
  189. {
  190. var fields = message.Descriptor.Fields;
  191. bool first = !assumeFirstFieldWritten;
  192. // First non-oneof fields
  193. foreach (var field in fields.InFieldNumberOrder())
  194. {
  195. var accessor = field.Accessor;
  196. if (field.ContainingOneof != null && field.ContainingOneof.Accessor.GetCaseFieldDescriptor(message) != field)
  197. {
  198. continue;
  199. }
  200. // Omit default values unless we're asked to format them, or they're oneofs (where the default
  201. // value is still formatted regardless, because that's how we preserve the oneof case).
  202. object value = accessor.GetValue(message);
  203. if (field.ContainingOneof == null && !settings.FormatDefaultValues && IsDefaultValue(accessor, value))
  204. {
  205. continue;
  206. }
  207. // Okay, all tests complete: let's write the field value...
  208. if (!first)
  209. {
  210. builder.Append(PropertySeparator);
  211. }
  212. WriteString(builder, ToCamelCase(accessor.Descriptor.Name));
  213. builder.Append(NameValueSeparator);
  214. WriteValue(builder, value);
  215. first = false;
  216. }
  217. return !first;
  218. }
  219. /// <summary>
  220. /// Camel-case converter with added strictness for field mask formatting.
  221. /// </summary>
  222. /// <exception cref="InvalidOperationException">The field mask is invalid for JSON representation</exception>
  223. private static string ToCamelCaseForFieldMask(string input)
  224. {
  225. for (int i = 0; i < input.Length; i++)
  226. {
  227. char c = input[i];
  228. if (c >= 'A' && c <= 'Z')
  229. {
  230. throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
  231. }
  232. if (c == '_' && i < input.Length - 1)
  233. {
  234. char next = input[i + 1];
  235. if (next < 'a' || next > 'z')
  236. {
  237. throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
  238. }
  239. }
  240. }
  241. return ToCamelCase(input);
  242. }
  243. // Converted from src/google/protobuf/util/internal/utility.cc ToCamelCase
  244. // TODO: Use the new field in FieldDescriptor.
  245. internal static string ToCamelCase(string input)
  246. {
  247. bool capitalizeNext = false;
  248. bool wasCap = true;
  249. bool isCap = false;
  250. bool firstWord = true;
  251. StringBuilder result = new StringBuilder(input.Length);
  252. for (int i = 0; i < input.Length; i++, wasCap = isCap)
  253. {
  254. isCap = char.IsUpper(input[i]);
  255. if (input[i] == '_')
  256. {
  257. capitalizeNext = true;
  258. if (result.Length != 0)
  259. {
  260. firstWord = false;
  261. }
  262. continue;
  263. }
  264. else if (firstWord)
  265. {
  266. // Consider when the current character B is capitalized,
  267. // first word ends when:
  268. // 1) following a lowercase: "...aB..."
  269. // 2) followed by a lowercase: "...ABc..."
  270. if (result.Length != 0 && isCap &&
  271. (!wasCap || (i + 1 < input.Length && char.IsLower(input[i + 1]))))
  272. {
  273. firstWord = false;
  274. }
  275. else
  276. {
  277. result.Append(char.ToLowerInvariant(input[i]));
  278. continue;
  279. }
  280. }
  281. else if (capitalizeNext)
  282. {
  283. capitalizeNext = false;
  284. if (char.IsLower(input[i]))
  285. {
  286. result.Append(char.ToUpperInvariant(input[i]));
  287. continue;
  288. }
  289. }
  290. result.Append(input[i]);
  291. }
  292. return result.ToString();
  293. }
  294. private static void WriteNull(StringBuilder builder)
  295. {
  296. builder.Append("null");
  297. }
  298. private static bool IsDefaultValue(IFieldAccessor accessor, object value)
  299. {
  300. if (accessor.Descriptor.IsMap)
  301. {
  302. IDictionary dictionary = (IDictionary) value;
  303. return dictionary.Count == 0;
  304. }
  305. if (accessor.Descriptor.IsRepeated)
  306. {
  307. IList list = (IList) value;
  308. return list.Count == 0;
  309. }
  310. switch (accessor.Descriptor.FieldType)
  311. {
  312. case FieldType.Bool:
  313. return (bool) value == false;
  314. case FieldType.Bytes:
  315. return (ByteString) value == ByteString.Empty;
  316. case FieldType.String:
  317. return (string) value == "";
  318. case FieldType.Double:
  319. return (double) value == 0.0;
  320. case FieldType.SInt32:
  321. case FieldType.Int32:
  322. case FieldType.SFixed32:
  323. case FieldType.Enum:
  324. return (int) value == 0;
  325. case FieldType.Fixed32:
  326. case FieldType.UInt32:
  327. return (uint) value == 0;
  328. case FieldType.Fixed64:
  329. case FieldType.UInt64:
  330. return (ulong) value == 0;
  331. case FieldType.SFixed64:
  332. case FieldType.Int64:
  333. case FieldType.SInt64:
  334. return (long) value == 0;
  335. case FieldType.Float:
  336. return (float) value == 0f;
  337. case FieldType.Message:
  338. case FieldType.Group: // Never expect to get this, but...
  339. return value == null;
  340. default:
  341. throw new ArgumentException("Invalid field type");
  342. }
  343. }
  344. private void WriteValue(StringBuilder builder, object value)
  345. {
  346. if (value == null)
  347. {
  348. WriteNull(builder);
  349. }
  350. else if (value is bool)
  351. {
  352. builder.Append((bool) value ? "true" : "false");
  353. }
  354. else if (value is ByteString)
  355. {
  356. // Nothing in Base64 needs escaping
  357. builder.Append('"');
  358. builder.Append(((ByteString) value).ToBase64());
  359. builder.Append('"');
  360. }
  361. else if (value is string)
  362. {
  363. WriteString(builder, (string) value);
  364. }
  365. else if (value is IDictionary)
  366. {
  367. WriteDictionary(builder, (IDictionary) value);
  368. }
  369. else if (value is IList)
  370. {
  371. WriteList(builder, (IList) value);
  372. }
  373. else if (value is int || value is uint)
  374. {
  375. IFormattable formattable = (IFormattable) value;
  376. builder.Append(formattable.ToString("d", CultureInfo.InvariantCulture));
  377. }
  378. else if (value is long || value is ulong)
  379. {
  380. builder.Append('"');
  381. IFormattable formattable = (IFormattable) value;
  382. builder.Append(formattable.ToString("d", CultureInfo.InvariantCulture));
  383. builder.Append('"');
  384. }
  385. else if (value is System.Enum)
  386. {
  387. if (System.Enum.IsDefined(value.GetType(), value))
  388. {
  389. WriteString(builder, value.ToString());
  390. }
  391. else
  392. {
  393. WriteValue(builder, (int) value);
  394. }
  395. }
  396. else if (value is float || value is double)
  397. {
  398. string text = ((IFormattable) value).ToString("r", CultureInfo.InvariantCulture);
  399. if (text == "NaN" || text == "Infinity" || text == "-Infinity")
  400. {
  401. builder.Append('"');
  402. builder.Append(text);
  403. builder.Append('"');
  404. }
  405. else
  406. {
  407. builder.Append(text);
  408. }
  409. }
  410. else if (value is IMessage)
  411. {
  412. IMessage message = (IMessage) value;
  413. if (message.Descriptor.IsWellKnownType)
  414. {
  415. WriteWellKnownTypeValue(builder, message.Descriptor, value);
  416. }
  417. else
  418. {
  419. WriteMessage(builder, (IMessage) value);
  420. }
  421. }
  422. else
  423. {
  424. throw new ArgumentException("Unable to format value of type " + value.GetType());
  425. }
  426. }
  427. /// <summary>
  428. /// Central interception point for well-known type formatting. Any well-known types which
  429. /// don't need special handling can fall back to WriteMessage. We avoid assuming that the
  430. /// values are using the embedded well-known types, in order to allow for dynamic messages
  431. /// in the future.
  432. /// </summary>
  433. private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescriptor descriptor, object value)
  434. {
  435. // Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*,
  436. // this would do the right thing.
  437. if (value == null)
  438. {
  439. WriteNull(builder);
  440. return;
  441. }
  442. // For wrapper types, the value will either be the (possibly boxed) "native" value,
  443. // or the message itself if we're formatting it at the top level (e.g. just calling ToString on the object itself).
  444. // If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value,
  445. // and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string...
  446. // WriteValue will do the right thing.)
  447. if (descriptor.IsWrapperType)
  448. {
  449. if (value is IMessage)
  450. {
  451. var message = (IMessage) value;
  452. value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message);
  453. }
  454. WriteValue(builder, value);
  455. return;
  456. }
  457. if (descriptor.FullName == Timestamp.Descriptor.FullName)
  458. {
  459. WriteTimestamp(builder, (IMessage) value);
  460. return;
  461. }
  462. if (descriptor.FullName == Duration.Descriptor.FullName)
  463. {
  464. WriteDuration(builder, (IMessage) value);
  465. return;
  466. }
  467. if (descriptor.FullName == FieldMask.Descriptor.FullName)
  468. {
  469. WriteFieldMask(builder, (IMessage) value);
  470. return;
  471. }
  472. if (descriptor.FullName == Struct.Descriptor.FullName)
  473. {
  474. WriteStruct(builder, (IMessage) value);
  475. return;
  476. }
  477. if (descriptor.FullName == ListValue.Descriptor.FullName)
  478. {
  479. var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
  480. WriteList(builder, (IList) fieldAccessor.GetValue((IMessage) value));
  481. return;
  482. }
  483. if (descriptor.FullName == Value.Descriptor.FullName)
  484. {
  485. WriteStructFieldValue(builder, (IMessage) value);
  486. return;
  487. }
  488. if (descriptor.FullName == Any.Descriptor.FullName)
  489. {
  490. WriteAny(builder, (IMessage) value);
  491. return;
  492. }
  493. WriteMessage(builder, (IMessage) value);
  494. }
  495. private void WriteTimestamp(StringBuilder builder, IMessage value)
  496. {
  497. // TODO: In the common case where this *is* using the built-in Timestamp type, we could
  498. // avoid all the reflection at this point, by casting to Timestamp. In the interests of
  499. // avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
  500. // it still works in that case.
  501. int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value);
  502. long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value);
  503. builder.Append(Timestamp.ToJson(seconds, nanos, DiagnosticOnly));
  504. }
  505. private void WriteDuration(StringBuilder builder, IMessage value)
  506. {
  507. // TODO: Same as for WriteTimestamp
  508. int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
  509. long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
  510. builder.Append(Duration.ToJson(seconds, nanos, DiagnosticOnly));
  511. }
  512. private void WriteFieldMask(StringBuilder builder, IMessage value)
  513. {
  514. var paths = (IList<string>) value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value);
  515. builder.Append(FieldMask.ToJson(paths, DiagnosticOnly));
  516. }
  517. private void WriteAny(StringBuilder builder, IMessage value)
  518. {
  519. if (DiagnosticOnly)
  520. {
  521. WriteDiagnosticOnlyAny(builder, value);
  522. return;
  523. }
  524. string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
  525. ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
  526. string typeName = GetTypeName(typeUrl);
  527. MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
  528. if (descriptor == null)
  529. {
  530. throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
  531. }
  532. IMessage message = descriptor.Parser.ParseFrom(data);
  533. builder.Append("{ ");
  534. WriteString(builder, AnyTypeUrlField);
  535. builder.Append(NameValueSeparator);
  536. WriteString(builder, typeUrl);
  537. if (descriptor.IsWellKnownType)
  538. {
  539. builder.Append(PropertySeparator);
  540. WriteString(builder, AnyWellKnownTypeValueField);
  541. builder.Append(NameValueSeparator);
  542. WriteWellKnownTypeValue(builder, descriptor, message);
  543. }
  544. else
  545. {
  546. WriteMessageFields(builder, message, true);
  547. }
  548. builder.Append(" }");
  549. }
  550. private void WriteDiagnosticOnlyAny(StringBuilder builder, IMessage value)
  551. {
  552. string typeUrl = (string) value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
  553. ByteString data = (ByteString) value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
  554. builder.Append("{ ");
  555. WriteString(builder, AnyTypeUrlField);
  556. builder.Append(NameValueSeparator);
  557. WriteString(builder, typeUrl);
  558. builder.Append(PropertySeparator);
  559. WriteString(builder, AnyDiagnosticValueField);
  560. builder.Append(NameValueSeparator);
  561. builder.Append('"');
  562. builder.Append(data.ToBase64());
  563. builder.Append('"');
  564. builder.Append(" }");
  565. }
  566. internal static string GetTypeName(String typeUrl)
  567. {
  568. string[] parts = typeUrl.Split('/');
  569. if (parts.Length != 2 || parts[0] != TypeUrlPrefix)
  570. {
  571. throw new InvalidProtocolBufferException($"Invalid type url: {typeUrl}");
  572. }
  573. return parts[1];
  574. }
  575. private void WriteStruct(StringBuilder builder, IMessage message)
  576. {
  577. builder.Append("{ ");
  578. IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message);
  579. bool first = true;
  580. foreach (DictionaryEntry entry in fields)
  581. {
  582. string key = (string) entry.Key;
  583. IMessage value = (IMessage) entry.Value;
  584. if (string.IsNullOrEmpty(key) || value == null)
  585. {
  586. throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
  587. }
  588. if (!first)
  589. {
  590. builder.Append(PropertySeparator);
  591. }
  592. WriteString(builder, key);
  593. builder.Append(NameValueSeparator);
  594. WriteStructFieldValue(builder, value);
  595. first = false;
  596. }
  597. builder.Append(first ? "}" : " }");
  598. }
  599. private void WriteStructFieldValue(StringBuilder builder, IMessage message)
  600. {
  601. var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message);
  602. if (specifiedField == null)
  603. {
  604. throw new InvalidOperationException("Value message must contain a value for the oneof.");
  605. }
  606. object value = specifiedField.Accessor.GetValue(message);
  607. switch (specifiedField.FieldNumber)
  608. {
  609. case Value.BoolValueFieldNumber:
  610. case Value.StringValueFieldNumber:
  611. case Value.NumberValueFieldNumber:
  612. WriteValue(builder, value);
  613. return;
  614. case Value.StructValueFieldNumber:
  615. case Value.ListValueFieldNumber:
  616. // Structs and ListValues are nested messages, and already well-known types.
  617. var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message);
  618. WriteWellKnownTypeValue(builder, nestedMessage.Descriptor, nestedMessage);
  619. return;
  620. case Value.NullValueFieldNumber:
  621. WriteNull(builder);
  622. return;
  623. default:
  624. throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber);
  625. }
  626. }
  627. internal void WriteList(StringBuilder builder, IList list)
  628. {
  629. builder.Append("[ ");
  630. bool first = true;
  631. foreach (var value in list)
  632. {
  633. if (!first)
  634. {
  635. builder.Append(PropertySeparator);
  636. }
  637. WriteValue(builder, value);
  638. first = false;
  639. }
  640. builder.Append(first ? "]" : " ]");
  641. }
  642. internal void WriteDictionary(StringBuilder builder, IDictionary dictionary)
  643. {
  644. builder.Append("{ ");
  645. bool first = true;
  646. // This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
  647. foreach (DictionaryEntry pair in dictionary)
  648. {
  649. if (!first)
  650. {
  651. builder.Append(PropertySeparator);
  652. }
  653. string keyText;
  654. if (pair.Key is string)
  655. {
  656. keyText = (string) pair.Key;
  657. }
  658. else if (pair.Key is bool)
  659. {
  660. keyText = (bool) pair.Key ? "true" : "false";
  661. }
  662. else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
  663. {
  664. keyText = ((IFormattable) pair.Key).ToString("d", CultureInfo.InvariantCulture);
  665. }
  666. else
  667. {
  668. if (pair.Key == null)
  669. {
  670. throw new ArgumentException("Dictionary has entry with null key");
  671. }
  672. throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
  673. }
  674. WriteString(builder, keyText);
  675. builder.Append(NameValueSeparator);
  676. WriteValue(builder, pair.Value);
  677. first = false;
  678. }
  679. builder.Append(first ? "}" : " }");
  680. }
  681. /// <summary>
  682. /// Returns whether or not a singular value can be represented in JSON.
  683. /// Currently only relevant for enums, where unknown values can't be represented.
  684. /// For repeated/map fields, this always returns true.
  685. /// </summary>
  686. private bool CanWriteSingleValue(object value)
  687. {
  688. if (value is System.Enum)
  689. {
  690. return System.Enum.IsDefined(value.GetType(), value);
  691. }
  692. return true;
  693. }
  694. /// <summary>
  695. /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
  696. /// </summary>
  697. /// <remarks>
  698. /// Other than surrogate pair handling, this code is mostly taken from src/google/protobuf/util/internal/json_escaping.cc.
  699. /// </remarks>
  700. internal static void WriteString(StringBuilder builder, string text)
  701. {
  702. builder.Append('"');
  703. for (int i = 0; i < text.Length; i++)
  704. {
  705. char c = text[i];
  706. if (c < 0xa0)
  707. {
  708. builder.Append(CommonRepresentations[c]);
  709. continue;
  710. }
  711. if (char.IsHighSurrogate(c))
  712. {
  713. // Encountered first part of a surrogate pair.
  714. // Check that we have the whole pair, and encode both parts as hex.
  715. i++;
  716. if (i == text.Length || !char.IsLowSurrogate(text[i]))
  717. {
  718. throw new ArgumentException("String contains low surrogate not followed by high surrogate");
  719. }
  720. HexEncodeUtf16CodeUnit(builder, c);
  721. HexEncodeUtf16CodeUnit(builder, text[i]);
  722. continue;
  723. }
  724. else if (char.IsLowSurrogate(c))
  725. {
  726. throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
  727. }
  728. switch ((uint) c)
  729. {
  730. // These are not required by json spec
  731. // but used to prevent security bugs in javascript.
  732. case 0xfeff: // Zero width no-break space
  733. case 0xfff9: // Interlinear annotation anchor
  734. case 0xfffa: // Interlinear annotation separator
  735. case 0xfffb: // Interlinear annotation terminator
  736. case 0x00ad: // Soft-hyphen
  737. case 0x06dd: // Arabic end of ayah
  738. case 0x070f: // Syriac abbreviation mark
  739. case 0x17b4: // Khmer vowel inherent Aq
  740. case 0x17b5: // Khmer vowel inherent Aa
  741. HexEncodeUtf16CodeUnit(builder, c);
  742. break;
  743. default:
  744. if ((c >= 0x0600 && c <= 0x0603) || // Arabic signs
  745. (c >= 0x200b && c <= 0x200f) || // Zero width etc.
  746. (c >= 0x2028 && c <= 0x202e) || // Separators etc.
  747. (c >= 0x2060 && c <= 0x2064) || // Invisible etc.
  748. (c >= 0x206a && c <= 0x206f))
  749. {
  750. HexEncodeUtf16CodeUnit(builder, c);
  751. }
  752. else
  753. {
  754. // No handling of surrogates here - that's done earlier
  755. builder.Append(c);
  756. }
  757. break;
  758. }
  759. }
  760. builder.Append('"');
  761. }
  762. private const string Hex = "0123456789abcdef";
  763. private static void HexEncodeUtf16CodeUnit(StringBuilder builder, char c)
  764. {
  765. builder.Append("\\u");
  766. builder.Append(Hex[(c >> 12) & 0xf]);
  767. builder.Append(Hex[(c >> 8) & 0xf]);
  768. builder.Append(Hex[(c >> 4) & 0xf]);
  769. builder.Append(Hex[(c >> 0) & 0xf]);
  770. }
  771. /// <summary>
  772. /// Settings controlling JSON formatting.
  773. /// </summary>
  774. public sealed class Settings
  775. {
  776. /// <summary>
  777. /// Default settings, as used by <see cref="JsonFormatter.Default"/>
  778. /// </summary>
  779. public static Settings Default { get; }
  780. // Workaround for the Mono compiler complaining about XML comments not being on
  781. // valid language elements.
  782. static Settings()
  783. {
  784. Default = new Settings(false);
  785. }
  786. /// <summary>
  787. /// Whether fields whose values are the default for the field type (e.g. 0 for integers)
  788. /// should be formatted (true) or omitted (false).
  789. /// </summary>
  790. public bool FormatDefaultValues { get; }
  791. /// <summary>
  792. /// The type registry used to format <see cref="Any"/> messages.
  793. /// </summary>
  794. public TypeRegistry TypeRegistry { get; }
  795. // TODO: Work out how we're going to scale this to multiple settings. "WithXyz" methods?
  796. /// <summary>
  797. /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
  798. /// and an empty type registry.
  799. /// </summary>
  800. /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
  801. public Settings(bool formatDefaultValues) : this(formatDefaultValues, TypeRegistry.Empty)
  802. {
  803. }
  804. /// <summary>
  805. /// Creates a new <see cref="Settings"/> object with the specified formatting of default values
  806. /// and type registry.
  807. /// </summary>
  808. /// <param name="formatDefaultValues"><c>true</c> if default values (0, empty strings etc) should be formatted; <c>false</c> otherwise.</param>
  809. /// <param name="typeRegistry">The <see cref="TypeRegistry"/> to use when formatting <see cref="Any"/> messages.</param>
  810. public Settings(bool formatDefaultValues, TypeRegistry typeRegistry)
  811. {
  812. FormatDefaultValues = formatDefaultValues;
  813. TypeRegistry = Preconditions.CheckNotNull(typeRegistry, nameof(typeRegistry));
  814. }
  815. }
  816. }
  817. }