JsonParser.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  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 Google.Protobuf.Reflection;
  33. using Google.Protobuf.WellKnownTypes;
  34. using System;
  35. using System.Collections;
  36. using System.Collections.Generic;
  37. using System.Globalization;
  38. using System.IO;
  39. using System.Text;
  40. using System.Text.RegularExpressions;
  41. namespace Google.Protobuf
  42. {
  43. /// <summary>
  44. /// Reflection-based converter from JSON to messages.
  45. /// </summary>
  46. /// <remarks>
  47. /// <para>
  48. /// Instances of this class are thread-safe, with no mutable state.
  49. /// </para>
  50. /// <para>
  51. /// This is a simple start to get JSON parsing working. As it's reflection-based,
  52. /// it's not as quick as baking calls into generated messages - but is a simpler implementation.
  53. /// (This code is generally not heavily optimized.)
  54. /// </para>
  55. /// </remarks>
  56. public sealed class JsonParser
  57. {
  58. // Note: using 0-9 instead of \d to ensure no non-ASCII digits.
  59. // This regex isn't a complete validator, but will remove *most* invalid input. We rely on parsing to do the rest.
  60. private static readonly Regex TimestampRegex = new Regex(@"^(?<datetime>[0-9]{4}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-5][0-9])(?<subseconds>\.[0-9]{1,9})?(?<offset>(Z|[+-][0-1][0-9]:[0-5][0-9]))$", FrameworkPortability.CompiledRegexWhereAvailable);
  61. private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", FrameworkPortability.CompiledRegexWhereAvailable);
  62. private static readonly int[] SubsecondScalingFactors = { 0, 100000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
  63. private static readonly char[] FieldMaskPathSeparators = new[] { ',' };
  64. private static readonly JsonParser defaultInstance = new JsonParser(Settings.Default);
  65. private static readonly Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
  66. WellKnownTypeHandlers = new Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
  67. {
  68. { Timestamp.Descriptor.FullName, (parser, message, tokenizer) => MergeTimestamp(message, tokenizer.Next()) },
  69. { Duration.Descriptor.FullName, (parser, message, tokenizer) => MergeDuration(message, tokenizer.Next()) },
  70. { Value.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStructValue(message, tokenizer) },
  71. { ListValue.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeRepeatedField(message, message.Descriptor.Fields[ListValue.ValuesFieldNumber], tokenizer) },
  72. { Struct.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStruct(message, tokenizer) },
  73. { FieldMask.Descriptor.FullName, (parser, message, tokenizer) => MergeFieldMask(message, tokenizer.Next()) },
  74. { Int32Value.Descriptor.FullName, MergeWrapperField },
  75. { Int64Value.Descriptor.FullName, MergeWrapperField },
  76. { UInt32Value.Descriptor.FullName, MergeWrapperField },
  77. { UInt64Value.Descriptor.FullName, MergeWrapperField },
  78. { FloatValue.Descriptor.FullName, MergeWrapperField },
  79. { DoubleValue.Descriptor.FullName, MergeWrapperField },
  80. { BytesValue.Descriptor.FullName, MergeWrapperField },
  81. { StringValue.Descriptor.FullName, MergeWrapperField }
  82. };
  83. // Convenience method to avoid having to repeat the same code multiple times in the above
  84. // dictionary initialization.
  85. private static void MergeWrapperField(JsonParser parser, IMessage message, JsonTokenizer tokenizer)
  86. {
  87. parser.MergeField(message, message.Descriptor.Fields[Wrappers.WrapperValueFieldNumber], tokenizer);
  88. }
  89. /// <summary>
  90. /// Returns a formatter using the default settings. /// </summary>
  91. public static JsonParser Default { get { return defaultInstance; } }
  92. // Currently the settings are unused.
  93. // TODO: When we've implemented Any (and the json spec is finalized), revisit whether they're
  94. // needed at all.
  95. #pragma warning disable 0414
  96. private readonly Settings settings;
  97. #pragma warning restore 0414
  98. /// <summary>
  99. /// Creates a new formatted with the given settings.
  100. /// </summary>
  101. /// <param name="settings">The settings.</param>
  102. public JsonParser(Settings settings)
  103. {
  104. this.settings = settings;
  105. }
  106. /// <summary>
  107. /// Parses <paramref name="json"/> and merges the information into the given message.
  108. /// </summary>
  109. /// <param name="message">The message to merge the JSON information into.</param>
  110. /// <param name="json">The JSON to parse.</param>
  111. internal void Merge(IMessage message, string json)
  112. {
  113. Merge(message, new StringReader(json));
  114. }
  115. /// <summary>
  116. /// Parses JSON read from <paramref name="jsonReader"/> and merges the information into the given message.
  117. /// </summary>
  118. /// <param name="message">The message to merge the JSON information into.</param>
  119. /// <param name="jsonReader">Reader providing the JSON to parse.</param>
  120. internal void Merge(IMessage message, TextReader jsonReader)
  121. {
  122. var tokenizer = new JsonTokenizer(jsonReader);
  123. Merge(message, tokenizer);
  124. var lastToken = tokenizer.Next();
  125. if (lastToken != JsonToken.EndDocument)
  126. {
  127. throw new InvalidProtocolBufferException("Expected end of JSON after object");
  128. }
  129. }
  130. /// <summary>
  131. /// Merges the given message using data from the given tokenizer. In most cases, the next
  132. /// token should be a "start object" token, but wrapper types and nullity can invalidate
  133. /// that assumption. This is implemented as an LL(1) recursive descent parser over the stream
  134. /// of tokens provided by the tokenizer. This token stream is assumed to be valid JSON, with the
  135. /// tokenizer performing that validation - but not every token stream is valid "protobuf JSON".
  136. /// </summary>
  137. private void Merge(IMessage message, JsonTokenizer tokenizer)
  138. {
  139. if (message.Descriptor.IsWellKnownType)
  140. {
  141. Action<JsonParser, IMessage, JsonTokenizer> handler;
  142. if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullName, out handler))
  143. {
  144. handler(this, message, tokenizer);
  145. return;
  146. }
  147. // Well-known types with no special handling continue in the normal way.
  148. }
  149. var token = tokenizer.Next();
  150. if (token.Type != JsonToken.TokenType.StartObject)
  151. {
  152. throw new InvalidProtocolBufferException("Expected an object");
  153. }
  154. var descriptor = message.Descriptor;
  155. var jsonFieldMap = descriptor.Fields.ByJsonName();
  156. while (true)
  157. {
  158. token = tokenizer.Next();
  159. if (token.Type == JsonToken.TokenType.EndObject)
  160. {
  161. return;
  162. }
  163. if (token.Type != JsonToken.TokenType.Name)
  164. {
  165. throw new InvalidOperationException("Unexpected token type " + token.Type);
  166. }
  167. string name = token.StringValue;
  168. FieldDescriptor field;
  169. if (jsonFieldMap.TryGetValue(name, out field))
  170. {
  171. MergeField(message, field, tokenizer);
  172. }
  173. else
  174. {
  175. // TODO: Is this what we want to do? If not, we'll need to skip the value,
  176. // which may be an object or array. (We might want to put code in the tokenizer
  177. // to do that.)
  178. throw new InvalidProtocolBufferException("Unknown field: " + name);
  179. }
  180. }
  181. }
  182. private void MergeField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
  183. {
  184. var token = tokenizer.Next();
  185. if (token.Type == JsonToken.TokenType.Null)
  186. {
  187. // Note: different from Java API, which just ignores it.
  188. // TODO: Bring it more in line? Discuss...
  189. field.Accessor.Clear(message);
  190. return;
  191. }
  192. tokenizer.PushBack(token);
  193. if (field.IsMap)
  194. {
  195. MergeMapField(message, field, tokenizer);
  196. }
  197. else if (field.IsRepeated)
  198. {
  199. MergeRepeatedField(message, field, tokenizer);
  200. }
  201. else
  202. {
  203. var value = ParseSingleValue(field, tokenizer);
  204. field.Accessor.SetValue(message, value);
  205. }
  206. }
  207. private void MergeRepeatedField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
  208. {
  209. var token = tokenizer.Next();
  210. if (token.Type != JsonToken.TokenType.StartArray)
  211. {
  212. throw new InvalidProtocolBufferException("Repeated field value was not an array. Token type: " + token.Type);
  213. }
  214. IList list = (IList) field.Accessor.GetValue(message);
  215. while (true)
  216. {
  217. token = tokenizer.Next();
  218. if (token.Type == JsonToken.TokenType.EndArray)
  219. {
  220. return;
  221. }
  222. tokenizer.PushBack(token);
  223. list.Add(ParseSingleValue(field, tokenizer));
  224. }
  225. }
  226. private void MergeMapField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
  227. {
  228. // Map fields are always objects, even if the values are well-known types: ParseSingleValue handles those.
  229. var token = tokenizer.Next();
  230. if (token.Type != JsonToken.TokenType.StartObject)
  231. {
  232. throw new InvalidProtocolBufferException("Expected an object to populate a map");
  233. }
  234. var type = field.MessageType;
  235. var keyField = type.FindFieldByNumber(1);
  236. var valueField = type.FindFieldByNumber(2);
  237. if (keyField == null || valueField == null)
  238. {
  239. throw new InvalidProtocolBufferException("Invalid map field: " + field.FullName);
  240. }
  241. IDictionary dictionary = (IDictionary) field.Accessor.GetValue(message);
  242. while (true)
  243. {
  244. token = tokenizer.Next();
  245. if (token.Type == JsonToken.TokenType.EndObject)
  246. {
  247. return;
  248. }
  249. object key = ParseMapKey(keyField, token.StringValue);
  250. object value = ParseSingleValue(valueField, tokenizer);
  251. // TODO: Null handling
  252. dictionary[key] = value;
  253. }
  254. }
  255. private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
  256. {
  257. var token = tokenizer.Next();
  258. if (token.Type == JsonToken.TokenType.Null)
  259. {
  260. if (field.FieldType == FieldType.Message && field.MessageType.FullName == Value.Descriptor.FullName)
  261. {
  262. return new Value { NullValue = NullValue.NULL_VALUE };
  263. }
  264. return null;
  265. }
  266. var fieldType = field.FieldType;
  267. if (fieldType == FieldType.Message)
  268. {
  269. // Parse wrapper types as their constituent types.
  270. // TODO: What does this mean for null?
  271. // TODO: Detect this differently when we have dynamic messages, and put it in one place...
  272. if (field.MessageType.IsWellKnownType && field.MessageType.File == Int32Value.Descriptor.File)
  273. {
  274. field = field.MessageType.Fields[Wrappers.WrapperValueFieldNumber];
  275. fieldType = field.FieldType;
  276. }
  277. else
  278. {
  279. // TODO: Merge the current value in message? (Public API currently doesn't make this relevant as we don't expose merging.)
  280. tokenizer.PushBack(token);
  281. IMessage subMessage = NewMessageForField(field);
  282. Merge(subMessage, tokenizer);
  283. return subMessage;
  284. }
  285. }
  286. switch (token.Type)
  287. {
  288. case JsonToken.TokenType.True:
  289. case JsonToken.TokenType.False:
  290. if (fieldType == FieldType.Bool)
  291. {
  292. return token.Type == JsonToken.TokenType.True;
  293. }
  294. // Fall through to "we don't support this type for this case"; could duplicate the behaviour of the default
  295. // case instead, but this way we'd only need to change one place.
  296. goto default;
  297. case JsonToken.TokenType.StringValue:
  298. return ParseSingleStringValue(field, token.StringValue);
  299. // Note: not passing the number value itself here, as we may end up storing the string value in the token too.
  300. case JsonToken.TokenType.Number:
  301. return ParseSingleNumberValue(field, token);
  302. case JsonToken.TokenType.Null:
  303. throw new NotImplementedException("Haven't worked out what to do for null yet");
  304. default:
  305. throw new InvalidProtocolBufferException("Unsupported JSON token type " + token.Type + " for field type " + fieldType);
  306. }
  307. }
  308. /// <summary>
  309. /// Parses <paramref name="json"/> into a new message.
  310. /// </summary>
  311. /// <typeparam name="T">The type of message to create.</typeparam>
  312. /// <param name="json">The JSON to parse.</param>
  313. public T Parse<T>(string json) where T : IMessage, new()
  314. {
  315. return Parse<T>(new StringReader(json));
  316. }
  317. /// <summary>
  318. /// Parses JSON read from <paramref name="jsonReader"/> into a new message.
  319. /// </summary>
  320. /// <typeparam name="T">The type of message to create.</typeparam>
  321. /// <param name="jsonReader">Reader providing the JSON to parse.</param>
  322. public T Parse<T>(TextReader jsonReader) where T : IMessage, new()
  323. {
  324. T message = new T();
  325. Merge(message, jsonReader);
  326. return message;
  327. }
  328. private void MergeStructValue(IMessage message, JsonTokenizer tokenizer)
  329. {
  330. var firstToken = tokenizer.Next();
  331. var fields = message.Descriptor.Fields;
  332. switch (firstToken.Type)
  333. {
  334. case JsonToken.TokenType.Null:
  335. fields[Value.NullValueFieldNumber].Accessor.SetValue(message, 0);
  336. return;
  337. case JsonToken.TokenType.StringValue:
  338. fields[Value.StringValueFieldNumber].Accessor.SetValue(message, firstToken.StringValue);
  339. return;
  340. case JsonToken.TokenType.Number:
  341. fields[Value.NumberValueFieldNumber].Accessor.SetValue(message, firstToken.NumberValue);
  342. return;
  343. case JsonToken.TokenType.False:
  344. case JsonToken.TokenType.True:
  345. fields[Value.BoolValueFieldNumber].Accessor.SetValue(message, firstToken.Type == JsonToken.TokenType.True);
  346. return;
  347. case JsonToken.TokenType.StartObject:
  348. {
  349. var field = fields[Value.StructValueFieldNumber];
  350. var structMessage = NewMessageForField(field);
  351. tokenizer.PushBack(firstToken);
  352. Merge(structMessage, tokenizer);
  353. field.Accessor.SetValue(message, structMessage);
  354. return;
  355. }
  356. case JsonToken.TokenType.StartArray:
  357. {
  358. var field = fields[Value.ListValueFieldNumber];
  359. var list = NewMessageForField(field);
  360. tokenizer.PushBack(firstToken);
  361. Merge(list, tokenizer);
  362. field.Accessor.SetValue(message, list);
  363. return;
  364. }
  365. default:
  366. throw new InvalidOperationException("Unexpected token type: " + firstToken.Type);
  367. }
  368. }
  369. private void MergeStruct(IMessage message, JsonTokenizer tokenizer)
  370. {
  371. var token = tokenizer.Next();
  372. if (token.Type != JsonToken.TokenType.StartObject)
  373. {
  374. throw new InvalidProtocolBufferException("Expected object value for Struct");
  375. }
  376. tokenizer.PushBack(token);
  377. var field = message.Descriptor.Fields[Struct.FieldsFieldNumber];
  378. MergeMapField(message, field, tokenizer);
  379. }
  380. #region Utility methods which don't depend on the state (or settings) of the parser.
  381. private static object ParseMapKey(FieldDescriptor field, string keyText)
  382. {
  383. switch (field.FieldType)
  384. {
  385. case FieldType.Bool:
  386. if (keyText == "true")
  387. {
  388. return true;
  389. }
  390. if (keyText == "false")
  391. {
  392. return false;
  393. }
  394. throw new InvalidProtocolBufferException("Invalid string for bool map key: " + keyText);
  395. case FieldType.String:
  396. return keyText;
  397. case FieldType.Int32:
  398. case FieldType.SInt32:
  399. case FieldType.SFixed32:
  400. return ParseNumericString(keyText, int.Parse, false);
  401. case FieldType.UInt32:
  402. case FieldType.Fixed32:
  403. return ParseNumericString(keyText, uint.Parse, false);
  404. case FieldType.Int64:
  405. case FieldType.SInt64:
  406. case FieldType.SFixed64:
  407. return ParseNumericString(keyText, long.Parse, false);
  408. case FieldType.UInt64:
  409. case FieldType.Fixed64:
  410. return ParseNumericString(keyText, ulong.Parse, false);
  411. default:
  412. throw new InvalidProtocolBufferException("Invalid field type for map: " + field.FieldType);
  413. }
  414. }
  415. private static object ParseSingleNumberValue(FieldDescriptor field, JsonToken token)
  416. {
  417. double value = token.NumberValue;
  418. checked
  419. {
  420. // TODO: Validate that it's actually an integer, possibly in terms of the textual representation?
  421. try
  422. {
  423. switch (field.FieldType)
  424. {
  425. case FieldType.Int32:
  426. case FieldType.SInt32:
  427. case FieldType.SFixed32:
  428. return (int) value;
  429. case FieldType.UInt32:
  430. case FieldType.Fixed32:
  431. return (uint) value;
  432. case FieldType.Int64:
  433. case FieldType.SInt64:
  434. case FieldType.SFixed64:
  435. return (long) value;
  436. case FieldType.UInt64:
  437. case FieldType.Fixed64:
  438. return (ulong) value;
  439. case FieldType.Double:
  440. return value;
  441. case FieldType.Float:
  442. if (double.IsNaN(value))
  443. {
  444. return float.NaN;
  445. }
  446. if (value > float.MaxValue || value < float.MinValue)
  447. {
  448. if (double.IsPositiveInfinity(value))
  449. {
  450. return float.PositiveInfinity;
  451. }
  452. if (double.IsNegativeInfinity(value))
  453. {
  454. return float.NegativeInfinity;
  455. }
  456. throw new InvalidProtocolBufferException("Value out of range: " + value);
  457. }
  458. return (float) value;
  459. default:
  460. throw new InvalidProtocolBufferException("Unsupported conversion from JSON number for field type " + field.FieldType);
  461. }
  462. }
  463. catch (OverflowException)
  464. {
  465. throw new InvalidProtocolBufferException("Value out of range: " + value);
  466. }
  467. }
  468. }
  469. private static object ParseSingleStringValue(FieldDescriptor field, string text)
  470. {
  471. switch (field.FieldType)
  472. {
  473. case FieldType.String:
  474. return text;
  475. case FieldType.Bytes:
  476. return ByteString.FromBase64(text);
  477. case FieldType.Int32:
  478. case FieldType.SInt32:
  479. case FieldType.SFixed32:
  480. return ParseNumericString(text, int.Parse, false);
  481. case FieldType.UInt32:
  482. case FieldType.Fixed32:
  483. return ParseNumericString(text, uint.Parse, false);
  484. case FieldType.Int64:
  485. case FieldType.SInt64:
  486. case FieldType.SFixed64:
  487. return ParseNumericString(text, long.Parse, false);
  488. case FieldType.UInt64:
  489. case FieldType.Fixed64:
  490. return ParseNumericString(text, ulong.Parse, false);
  491. case FieldType.Double:
  492. double d = ParseNumericString(text, double.Parse, true);
  493. // double.Parse can return +/- infinity on Mono for non-infinite values which are out of range for double.
  494. if (double.IsInfinity(d) && !text.Contains("Infinity"))
  495. {
  496. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  497. }
  498. return d;
  499. case FieldType.Float:
  500. float f = ParseNumericString(text, float.Parse, true);
  501. // float.Parse can return +/- infinity on Mono for non-infinite values which are out of range for float.
  502. if (float.IsInfinity(f) && !text.Contains("Infinity"))
  503. {
  504. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  505. }
  506. return f;
  507. case FieldType.Enum:
  508. var enumValue = field.EnumType.FindValueByName(text);
  509. if (enumValue == null)
  510. {
  511. throw new InvalidProtocolBufferException("Invalid enum value: " + text + " for enum type: " + field.EnumType.FullName);
  512. }
  513. // Just return it as an int, and let the CLR convert it.
  514. return enumValue.Number;
  515. default:
  516. throw new InvalidProtocolBufferException("Unsupported conversion from JSON string for field type " + field.FieldType);
  517. }
  518. }
  519. /// <summary>
  520. /// Creates a new instance of the message type for the given field.
  521. /// This method is mostly extracted so we can replace it in one go when we work out
  522. /// what we want to do instead of Activator.CreateInstance.
  523. /// </summary>
  524. private static IMessage NewMessageForField(FieldDescriptor field)
  525. {
  526. // TODO: Create an instance in a better way ?
  527. // (We could potentially add a Parser property to MessageDescriptor... see issue 806.)
  528. return (IMessage) Activator.CreateInstance(field.MessageType.GeneratedType);
  529. }
  530. private static T ParseNumericString<T>(string text, Func<string, NumberStyles, IFormatProvider, T> parser, bool floatingPoint)
  531. {
  532. // TODO: Prohibit leading zeroes (but allow 0!)
  533. // TODO: Validate handling of "Infinity" etc. (Should be case sensitive, no leading whitespace etc)
  534. // Can't prohibit this with NumberStyles.
  535. if (text.StartsWith("+"))
  536. {
  537. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  538. }
  539. if (text.StartsWith("0") && text.Length > 1)
  540. {
  541. if (text[1] >= '0' && text[1] <= '9')
  542. {
  543. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  544. }
  545. }
  546. else if (text.StartsWith("-0") && text.Length > 2)
  547. {
  548. if (text[2] >= '0' && text[2] <= '9')
  549. {
  550. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  551. }
  552. }
  553. try
  554. {
  555. var styles = floatingPoint
  556. ? NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent
  557. : NumberStyles.AllowLeadingSign;
  558. return parser(text, styles, CultureInfo.InvariantCulture);
  559. }
  560. catch (FormatException)
  561. {
  562. throw new InvalidProtocolBufferException("Invalid numeric value for type: " + text);
  563. }
  564. catch (OverflowException)
  565. {
  566. throw new InvalidProtocolBufferException("Value out of range: " + text);
  567. }
  568. }
  569. private static void MergeTimestamp(IMessage message, JsonToken token)
  570. {
  571. if (token.Type != JsonToken.TokenType.StringValue)
  572. {
  573. throw new InvalidProtocolBufferException("Expected string value for Timestamp");
  574. }
  575. var match = TimestampRegex.Match(token.StringValue);
  576. if (!match.Success)
  577. {
  578. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  579. }
  580. var dateTime = match.Groups["datetime"].Value;
  581. var subseconds = match.Groups["subseconds"].Value;
  582. var offset = match.Groups["offset"].Value;
  583. try
  584. {
  585. DateTime parsed = DateTime.ParseExact(
  586. dateTime,
  587. "yyyy-MM-dd'T'HH:mm:ss",
  588. CultureInfo.InvariantCulture,
  589. DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
  590. // TODO: It would be nice not to have to create all these objects... easy to optimize later though.
  591. Timestamp timestamp = Timestamp.FromDateTime(parsed);
  592. int nanosToAdd = 0;
  593. if (subseconds != "")
  594. {
  595. // This should always work, as we've got 1-9 digits.
  596. int parsedFraction = int.Parse(subseconds.Substring(1), CultureInfo.InvariantCulture);
  597. nanosToAdd = parsedFraction * SubsecondScalingFactors[subseconds.Length];
  598. }
  599. int secondsToAdd = 0;
  600. if (offset != "Z")
  601. {
  602. // This is the amount we need to *subtract* from the local time to get to UTC - hence - => +1 and vice versa.
  603. int sign = offset[0] == '-' ? 1 : -1;
  604. int hours = int.Parse(offset.Substring(1, 2), CultureInfo.InvariantCulture);
  605. int minutes = int.Parse(offset.Substring(4, 2));
  606. int totalMinutes = hours * 60 + minutes;
  607. if (totalMinutes > 18 * 60)
  608. {
  609. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  610. }
  611. if (totalMinutes == 0 && sign == 1)
  612. {
  613. // This is an offset of -00:00, which means "unknown local offset". It makes no sense for a timestamp.
  614. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  615. }
  616. // We need to *subtract* the offset from local time to get UTC.
  617. secondsToAdd = sign * totalMinutes * 60;
  618. }
  619. // Ensure we've got the right signs. Currently unnecessary, but easy to do.
  620. if (secondsToAdd < 0 && nanosToAdd > 0)
  621. {
  622. secondsToAdd++;
  623. nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond;
  624. }
  625. if (secondsToAdd != 0 || nanosToAdd != 0)
  626. {
  627. timestamp += new Duration { Nanos = nanosToAdd, Seconds = secondsToAdd };
  628. // The resulting timestamp after offset change would be out of our expected range. Currently the Timestamp message doesn't validate this
  629. // anywhere, but we shouldn't parse it.
  630. if (timestamp.Seconds < Timestamp.UnixSecondsAtBclMinValue || timestamp.Seconds > Timestamp.UnixSecondsAtBclMaxValue)
  631. {
  632. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  633. }
  634. }
  635. message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.SetValue(message, timestamp.Seconds);
  636. message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.SetValue(message, timestamp.Nanos);
  637. }
  638. catch (FormatException)
  639. {
  640. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  641. }
  642. }
  643. private static void MergeDuration(IMessage message, JsonToken token)
  644. {
  645. if (token.Type != JsonToken.TokenType.StringValue)
  646. {
  647. throw new InvalidProtocolBufferException("Expected string value for Duration");
  648. }
  649. var match = DurationRegex.Match(token.StringValue);
  650. if (!match.Success)
  651. {
  652. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  653. }
  654. var sign = match.Groups["sign"].Value;
  655. var secondsText = match.Groups["int"].Value;
  656. // Prohibit leading insignficant zeroes
  657. if (secondsText[0] == '0' && secondsText.Length > 1)
  658. {
  659. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  660. }
  661. var subseconds = match.Groups["subseconds"].Value;
  662. var multiplier = sign == "-" ? -1 : 1;
  663. try
  664. {
  665. long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture);
  666. int nanos = 0;
  667. if (subseconds != "")
  668. {
  669. // This should always work, as we've got 1-9 digits.
  670. int parsedFraction = int.Parse(subseconds.Substring(1));
  671. nanos = parsedFraction * SubsecondScalingFactors[subseconds.Length];
  672. }
  673. if (seconds >= Duration.MaxSeconds)
  674. {
  675. // Allow precisely 315576000000 seconds, but prohibit even 1ns more.
  676. if (seconds > Duration.MaxSeconds || nanos > 0)
  677. {
  678. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  679. }
  680. }
  681. message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds * multiplier);
  682. message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos * multiplier);
  683. }
  684. catch (FormatException)
  685. {
  686. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  687. }
  688. }
  689. private static void MergeFieldMask(IMessage message, JsonToken token)
  690. {
  691. if (token.Type != JsonToken.TokenType.StringValue)
  692. {
  693. throw new InvalidProtocolBufferException("Expected string value for FieldMask");
  694. }
  695. // TODO: Do we *want* to remove empty entries? Probably okay to treat "" as "no paths", but "foo,,bar"?
  696. string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators, StringSplitOptions.RemoveEmptyEntries);
  697. IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(message);
  698. foreach (var path in jsonPaths)
  699. {
  700. messagePaths.Add(ToSnakeCase(path));
  701. }
  702. }
  703. // Ported from src/google/protobuf/util/internal/utility.cc
  704. private static string ToSnakeCase(string text)
  705. {
  706. var builder = new StringBuilder(text.Length * 2);
  707. bool wasNotUnderscore = false; // Initialize to false for case 1 (below)
  708. bool wasNotCap = false;
  709. for (int i = 0; i < text.Length; i++)
  710. {
  711. char c = text[i];
  712. if (c >= 'A' && c <= 'Z') // ascii_isupper
  713. {
  714. // Consider when the current character B is capitalized:
  715. // 1) At beginning of input: "B..." => "b..."
  716. // (e.g. "Biscuit" => "biscuit")
  717. // 2) Following a lowercase: "...aB..." => "...a_b..."
  718. // (e.g. "gBike" => "g_bike")
  719. // 3) At the end of input: "...AB" => "...ab"
  720. // (e.g. "GoogleLAB" => "google_lab")
  721. // 4) Followed by a lowercase: "...ABc..." => "...a_bc..."
  722. // (e.g. "GBike" => "g_bike")
  723. if (wasNotUnderscore && // case 1 out
  724. (wasNotCap || // case 2 in, case 3 out
  725. (i + 1 < text.Length && // case 3 out
  726. (text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii_islower(text[i + 1])
  727. { // case 4 in
  728. // We add an underscore for case 2 and case 4.
  729. builder.Append('_');
  730. }
  731. // ascii_tolower, but we already know that c *is* an upper case ASCII character...
  732. builder.Append((char) (c + 'a' - 'A'));
  733. wasNotUnderscore = true;
  734. wasNotCap = false;
  735. }
  736. else
  737. {
  738. builder.Append(c);
  739. wasNotUnderscore = c != '_';
  740. wasNotCap = true;
  741. }
  742. }
  743. return builder.ToString();
  744. }
  745. #endregion
  746. /// <summary>
  747. /// Settings controlling JSON parsing. (Currently doesn't have any actual settings, but I suspect
  748. /// we'll want them for levels of strictness, descriptor pools for Any handling, etc.)
  749. /// </summary>
  750. public sealed class Settings
  751. {
  752. private static readonly Settings defaultInstance = new Settings();
  753. // TODO: Add recursion limit.
  754. /// <summary>
  755. /// Default settings, as used by <see cref="JsonParser.Default"/>
  756. /// </summary>
  757. public static Settings Default { get { return defaultInstance; } }
  758. /// <summary>
  759. /// Creates a new <see cref="Settings"/> object.
  760. /// </summary>
  761. public Settings()
  762. {
  763. }
  764. }
  765. }
  766. }