1ffe3c632Sopenharmony_ci#region Copyright notice and license 2ffe3c632Sopenharmony_ci// Protocol Buffers - Google's data interchange format 3ffe3c632Sopenharmony_ci// Copyright 2015 Google Inc. All rights reserved. 4ffe3c632Sopenharmony_ci// https://developers.google.com/protocol-buffers/ 5ffe3c632Sopenharmony_ci// 6ffe3c632Sopenharmony_ci// Redistribution and use in source and binary forms, with or without 7ffe3c632Sopenharmony_ci// modification, are permitted provided that the following conditions are 8ffe3c632Sopenharmony_ci// met: 9ffe3c632Sopenharmony_ci// 10ffe3c632Sopenharmony_ci// * Redistributions of source code must retain the above copyright 11ffe3c632Sopenharmony_ci// notice, this list of conditions and the following disclaimer. 12ffe3c632Sopenharmony_ci// * Redistributions in binary form must reproduce the above 13ffe3c632Sopenharmony_ci// copyright notice, this list of conditions and the following disclaimer 14ffe3c632Sopenharmony_ci// in the documentation and/or other materials provided with the 15ffe3c632Sopenharmony_ci// distribution. 16ffe3c632Sopenharmony_ci// * Neither the name of Google Inc. nor the names of its 17ffe3c632Sopenharmony_ci// contributors may be used to endorse or promote products derived from 18ffe3c632Sopenharmony_ci// this software without specific prior written permission. 19ffe3c632Sopenharmony_ci// 20ffe3c632Sopenharmony_ci// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21ffe3c632Sopenharmony_ci// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22ffe3c632Sopenharmony_ci// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23ffe3c632Sopenharmony_ci// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24ffe3c632Sopenharmony_ci// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25ffe3c632Sopenharmony_ci// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26ffe3c632Sopenharmony_ci// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27ffe3c632Sopenharmony_ci// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28ffe3c632Sopenharmony_ci// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29ffe3c632Sopenharmony_ci// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30ffe3c632Sopenharmony_ci// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31ffe3c632Sopenharmony_ci#endregion 32ffe3c632Sopenharmony_ci 33ffe3c632Sopenharmony_ciusing Google.Protobuf.Reflection; 34ffe3c632Sopenharmony_ciusing Google.Protobuf.WellKnownTypes; 35ffe3c632Sopenharmony_ciusing System; 36ffe3c632Sopenharmony_ciusing System.Collections; 37ffe3c632Sopenharmony_ciusing System.Collections.Generic; 38ffe3c632Sopenharmony_ciusing System.Globalization; 39ffe3c632Sopenharmony_ciusing System.IO; 40ffe3c632Sopenharmony_ciusing System.Linq; 41ffe3c632Sopenharmony_ciusing System.Text; 42ffe3c632Sopenharmony_ciusing System.Text.RegularExpressions; 43ffe3c632Sopenharmony_ci 44ffe3c632Sopenharmony_cinamespace Google.Protobuf 45ffe3c632Sopenharmony_ci{ 46ffe3c632Sopenharmony_ci /// <summary> 47ffe3c632Sopenharmony_ci /// Reflection-based converter from JSON to messages. 48ffe3c632Sopenharmony_ci /// </summary> 49ffe3c632Sopenharmony_ci /// <remarks> 50ffe3c632Sopenharmony_ci /// <para> 51ffe3c632Sopenharmony_ci /// Instances of this class are thread-safe, with no mutable state. 52ffe3c632Sopenharmony_ci /// </para> 53ffe3c632Sopenharmony_ci /// <para> 54ffe3c632Sopenharmony_ci /// This is a simple start to get JSON parsing working. As it's reflection-based, 55ffe3c632Sopenharmony_ci /// it's not as quick as baking calls into generated messages - but is a simpler implementation. 56ffe3c632Sopenharmony_ci /// (This code is generally not heavily optimized.) 57ffe3c632Sopenharmony_ci /// </para> 58ffe3c632Sopenharmony_ci /// </remarks> 59ffe3c632Sopenharmony_ci public sealed class JsonParser 60ffe3c632Sopenharmony_ci { 61ffe3c632Sopenharmony_ci // Note: using 0-9 instead of \d to ensure no non-ASCII digits. 62ffe3c632Sopenharmony_ci // This regex isn't a complete validator, but will remove *most* invalid input. We rely on parsing to do the rest. 63ffe3c632Sopenharmony_ci 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); 64ffe3c632Sopenharmony_ci private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", FrameworkPortability.CompiledRegexWhereAvailable); 65ffe3c632Sopenharmony_ci private static readonly int[] SubsecondScalingFactors = { 0, 100000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 }; 66ffe3c632Sopenharmony_ci private static readonly char[] FieldMaskPathSeparators = new[] { ',' }; 67ffe3c632Sopenharmony_ci private static readonly EnumDescriptor NullValueDescriptor = StructReflection.Descriptor.EnumTypes.Single(ed => ed.ClrType == typeof(NullValue)); 68ffe3c632Sopenharmony_ci 69ffe3c632Sopenharmony_ci private static readonly JsonParser defaultInstance = new JsonParser(Settings.Default); 70ffe3c632Sopenharmony_ci 71ffe3c632Sopenharmony_ci // TODO: Consider introducing a class containing parse state of the parser, tokenizer and depth. That would simplify these handlers 72ffe3c632Sopenharmony_ci // and the signatures of various methods. 73ffe3c632Sopenharmony_ci private static readonly Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>> 74ffe3c632Sopenharmony_ci WellKnownTypeHandlers = new Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>> 75ffe3c632Sopenharmony_ci { 76ffe3c632Sopenharmony_ci { Timestamp.Descriptor.FullName, (parser, message, tokenizer) => MergeTimestamp(message, tokenizer.Next()) }, 77ffe3c632Sopenharmony_ci { Duration.Descriptor.FullName, (parser, message, tokenizer) => MergeDuration(message, tokenizer.Next()) }, 78ffe3c632Sopenharmony_ci { Value.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStructValue(message, tokenizer) }, 79ffe3c632Sopenharmony_ci { ListValue.Descriptor.FullName, (parser, message, tokenizer) => 80ffe3c632Sopenharmony_ci parser.MergeRepeatedField(message, message.Descriptor.Fields[ListValue.ValuesFieldNumber], tokenizer) }, 81ffe3c632Sopenharmony_ci { Struct.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStruct(message, tokenizer) }, 82ffe3c632Sopenharmony_ci { Any.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeAny(message, tokenizer) }, 83ffe3c632Sopenharmony_ci { FieldMask.Descriptor.FullName, (parser, message, tokenizer) => MergeFieldMask(message, tokenizer.Next()) }, 84ffe3c632Sopenharmony_ci { Int32Value.Descriptor.FullName, MergeWrapperField }, 85ffe3c632Sopenharmony_ci { Int64Value.Descriptor.FullName, MergeWrapperField }, 86ffe3c632Sopenharmony_ci { UInt32Value.Descriptor.FullName, MergeWrapperField }, 87ffe3c632Sopenharmony_ci { UInt64Value.Descriptor.FullName, MergeWrapperField }, 88ffe3c632Sopenharmony_ci { FloatValue.Descriptor.FullName, MergeWrapperField }, 89ffe3c632Sopenharmony_ci { DoubleValue.Descriptor.FullName, MergeWrapperField }, 90ffe3c632Sopenharmony_ci { BytesValue.Descriptor.FullName, MergeWrapperField }, 91ffe3c632Sopenharmony_ci { StringValue.Descriptor.FullName, MergeWrapperField }, 92ffe3c632Sopenharmony_ci { BoolValue.Descriptor.FullName, MergeWrapperField } 93ffe3c632Sopenharmony_ci }; 94ffe3c632Sopenharmony_ci 95ffe3c632Sopenharmony_ci // Convenience method to avoid having to repeat the same code multiple times in the above 96ffe3c632Sopenharmony_ci // dictionary initialization. 97ffe3c632Sopenharmony_ci private static void MergeWrapperField(JsonParser parser, IMessage message, JsonTokenizer tokenizer) 98ffe3c632Sopenharmony_ci { 99ffe3c632Sopenharmony_ci parser.MergeField(message, message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber], tokenizer); 100ffe3c632Sopenharmony_ci } 101ffe3c632Sopenharmony_ci 102ffe3c632Sopenharmony_ci /// <summary> 103ffe3c632Sopenharmony_ci /// Returns a formatter using the default settings. 104ffe3c632Sopenharmony_ci /// </summary> 105ffe3c632Sopenharmony_ci public static JsonParser Default { get { return defaultInstance; } } 106ffe3c632Sopenharmony_ci 107ffe3c632Sopenharmony_ci private readonly Settings settings; 108ffe3c632Sopenharmony_ci 109ffe3c632Sopenharmony_ci /// <summary> 110ffe3c632Sopenharmony_ci /// Creates a new formatted with the given settings. 111ffe3c632Sopenharmony_ci /// </summary> 112ffe3c632Sopenharmony_ci /// <param name="settings">The settings.</param> 113ffe3c632Sopenharmony_ci public JsonParser(Settings settings) 114ffe3c632Sopenharmony_ci { 115ffe3c632Sopenharmony_ci this.settings = ProtoPreconditions.CheckNotNull(settings, nameof(settings)); 116ffe3c632Sopenharmony_ci } 117ffe3c632Sopenharmony_ci 118ffe3c632Sopenharmony_ci /// <summary> 119ffe3c632Sopenharmony_ci /// Parses <paramref name="json"/> and merges the information into the given message. 120ffe3c632Sopenharmony_ci /// </summary> 121ffe3c632Sopenharmony_ci /// <param name="message">The message to merge the JSON information into.</param> 122ffe3c632Sopenharmony_ci /// <param name="json">The JSON to parse.</param> 123ffe3c632Sopenharmony_ci internal void Merge(IMessage message, string json) 124ffe3c632Sopenharmony_ci { 125ffe3c632Sopenharmony_ci Merge(message, new StringReader(json)); 126ffe3c632Sopenharmony_ci } 127ffe3c632Sopenharmony_ci 128ffe3c632Sopenharmony_ci /// <summary> 129ffe3c632Sopenharmony_ci /// Parses JSON read from <paramref name="jsonReader"/> and merges the information into the given message. 130ffe3c632Sopenharmony_ci /// </summary> 131ffe3c632Sopenharmony_ci /// <param name="message">The message to merge the JSON information into.</param> 132ffe3c632Sopenharmony_ci /// <param name="jsonReader">Reader providing the JSON to parse.</param> 133ffe3c632Sopenharmony_ci internal void Merge(IMessage message, TextReader jsonReader) 134ffe3c632Sopenharmony_ci { 135ffe3c632Sopenharmony_ci var tokenizer = JsonTokenizer.FromTextReader(jsonReader); 136ffe3c632Sopenharmony_ci Merge(message, tokenizer); 137ffe3c632Sopenharmony_ci var lastToken = tokenizer.Next(); 138ffe3c632Sopenharmony_ci if (lastToken != JsonToken.EndDocument) 139ffe3c632Sopenharmony_ci { 140ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected end of JSON after object"); 141ffe3c632Sopenharmony_ci } 142ffe3c632Sopenharmony_ci } 143ffe3c632Sopenharmony_ci 144ffe3c632Sopenharmony_ci /// <summary> 145ffe3c632Sopenharmony_ci /// Merges the given message using data from the given tokenizer. In most cases, the next 146ffe3c632Sopenharmony_ci /// token should be a "start object" token, but wrapper types and nullity can invalidate 147ffe3c632Sopenharmony_ci /// that assumption. This is implemented as an LL(1) recursive descent parser over the stream 148ffe3c632Sopenharmony_ci /// of tokens provided by the tokenizer. This token stream is assumed to be valid JSON, with the 149ffe3c632Sopenharmony_ci /// tokenizer performing that validation - but not every token stream is valid "protobuf JSON". 150ffe3c632Sopenharmony_ci /// </summary> 151ffe3c632Sopenharmony_ci private void Merge(IMessage message, JsonTokenizer tokenizer) 152ffe3c632Sopenharmony_ci { 153ffe3c632Sopenharmony_ci if (tokenizer.ObjectDepth > settings.RecursionLimit) 154ffe3c632Sopenharmony_ci { 155ffe3c632Sopenharmony_ci throw InvalidProtocolBufferException.JsonRecursionLimitExceeded(); 156ffe3c632Sopenharmony_ci } 157ffe3c632Sopenharmony_ci if (message.Descriptor.IsWellKnownType) 158ffe3c632Sopenharmony_ci { 159ffe3c632Sopenharmony_ci Action<JsonParser, IMessage, JsonTokenizer> handler; 160ffe3c632Sopenharmony_ci if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullName, out handler)) 161ffe3c632Sopenharmony_ci { 162ffe3c632Sopenharmony_ci handler(this, message, tokenizer); 163ffe3c632Sopenharmony_ci return; 164ffe3c632Sopenharmony_ci } 165ffe3c632Sopenharmony_ci // Well-known types with no special handling continue in the normal way. 166ffe3c632Sopenharmony_ci } 167ffe3c632Sopenharmony_ci var token = tokenizer.Next(); 168ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StartObject) 169ffe3c632Sopenharmony_ci { 170ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected an object"); 171ffe3c632Sopenharmony_ci } 172ffe3c632Sopenharmony_ci var descriptor = message.Descriptor; 173ffe3c632Sopenharmony_ci var jsonFieldMap = descriptor.Fields.ByJsonName(); 174ffe3c632Sopenharmony_ci // All the oneof fields we've already accounted for - we can only see each of them once. 175ffe3c632Sopenharmony_ci // The set is created lazily to avoid the overhead of creating a set for every message 176ffe3c632Sopenharmony_ci // we parsed, when oneofs are relatively rare. 177ffe3c632Sopenharmony_ci HashSet<OneofDescriptor> seenOneofs = null; 178ffe3c632Sopenharmony_ci while (true) 179ffe3c632Sopenharmony_ci { 180ffe3c632Sopenharmony_ci token = tokenizer.Next(); 181ffe3c632Sopenharmony_ci if (token.Type == JsonToken.TokenType.EndObject) 182ffe3c632Sopenharmony_ci { 183ffe3c632Sopenharmony_ci return; 184ffe3c632Sopenharmony_ci } 185ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.Name) 186ffe3c632Sopenharmony_ci { 187ffe3c632Sopenharmony_ci throw new InvalidOperationException("Unexpected token type " + token.Type); 188ffe3c632Sopenharmony_ci } 189ffe3c632Sopenharmony_ci string name = token.StringValue; 190ffe3c632Sopenharmony_ci FieldDescriptor field; 191ffe3c632Sopenharmony_ci if (jsonFieldMap.TryGetValue(name, out field)) 192ffe3c632Sopenharmony_ci { 193ffe3c632Sopenharmony_ci if (field.ContainingOneof != null) 194ffe3c632Sopenharmony_ci { 195ffe3c632Sopenharmony_ci if (seenOneofs == null) 196ffe3c632Sopenharmony_ci { 197ffe3c632Sopenharmony_ci seenOneofs = new HashSet<OneofDescriptor>(); 198ffe3c632Sopenharmony_ci } 199ffe3c632Sopenharmony_ci if (!seenOneofs.Add(field.ContainingOneof)) 200ffe3c632Sopenharmony_ci { 201ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Multiple values specified for oneof {field.ContainingOneof.Name}"); 202ffe3c632Sopenharmony_ci } 203ffe3c632Sopenharmony_ci } 204ffe3c632Sopenharmony_ci MergeField(message, field, tokenizer); 205ffe3c632Sopenharmony_ci } 206ffe3c632Sopenharmony_ci else 207ffe3c632Sopenharmony_ci { 208ffe3c632Sopenharmony_ci if (settings.IgnoreUnknownFields) 209ffe3c632Sopenharmony_ci { 210ffe3c632Sopenharmony_ci tokenizer.SkipValue(); 211ffe3c632Sopenharmony_ci } 212ffe3c632Sopenharmony_ci else 213ffe3c632Sopenharmony_ci { 214ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Unknown field: " + name); 215ffe3c632Sopenharmony_ci } 216ffe3c632Sopenharmony_ci } 217ffe3c632Sopenharmony_ci } 218ffe3c632Sopenharmony_ci } 219ffe3c632Sopenharmony_ci 220ffe3c632Sopenharmony_ci private void MergeField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer) 221ffe3c632Sopenharmony_ci { 222ffe3c632Sopenharmony_ci var token = tokenizer.Next(); 223ffe3c632Sopenharmony_ci if (token.Type == JsonToken.TokenType.Null) 224ffe3c632Sopenharmony_ci { 225ffe3c632Sopenharmony_ci // Clear the field if we see a null token, unless it's for a singular field of type 226ffe3c632Sopenharmony_ci // google.protobuf.Value or google.protobuf.NullValue. 227ffe3c632Sopenharmony_ci // Note: different from Java API, which just ignores it. 228ffe3c632Sopenharmony_ci // TODO: Bring it more in line? Discuss... 229ffe3c632Sopenharmony_ci if (field.IsMap || field.IsRepeated || 230ffe3c632Sopenharmony_ci !(IsGoogleProtobufValueField(field) || IsGoogleProtobufNullValueField(field))) 231ffe3c632Sopenharmony_ci { 232ffe3c632Sopenharmony_ci field.Accessor.Clear(message); 233ffe3c632Sopenharmony_ci return; 234ffe3c632Sopenharmony_ci } 235ffe3c632Sopenharmony_ci } 236ffe3c632Sopenharmony_ci tokenizer.PushBack(token); 237ffe3c632Sopenharmony_ci 238ffe3c632Sopenharmony_ci if (field.IsMap) 239ffe3c632Sopenharmony_ci { 240ffe3c632Sopenharmony_ci MergeMapField(message, field, tokenizer); 241ffe3c632Sopenharmony_ci } 242ffe3c632Sopenharmony_ci else if (field.IsRepeated) 243ffe3c632Sopenharmony_ci { 244ffe3c632Sopenharmony_ci MergeRepeatedField(message, field, tokenizer); 245ffe3c632Sopenharmony_ci } 246ffe3c632Sopenharmony_ci else 247ffe3c632Sopenharmony_ci { 248ffe3c632Sopenharmony_ci var value = ParseSingleValue(field, tokenizer); 249ffe3c632Sopenharmony_ci field.Accessor.SetValue(message, value); 250ffe3c632Sopenharmony_ci } 251ffe3c632Sopenharmony_ci } 252ffe3c632Sopenharmony_ci 253ffe3c632Sopenharmony_ci private void MergeRepeatedField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer) 254ffe3c632Sopenharmony_ci { 255ffe3c632Sopenharmony_ci var token = tokenizer.Next(); 256ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StartArray) 257ffe3c632Sopenharmony_ci { 258ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Repeated field value was not an array. Token type: " + token.Type); 259ffe3c632Sopenharmony_ci } 260ffe3c632Sopenharmony_ci 261ffe3c632Sopenharmony_ci IList list = (IList) field.Accessor.GetValue(message); 262ffe3c632Sopenharmony_ci while (true) 263ffe3c632Sopenharmony_ci { 264ffe3c632Sopenharmony_ci token = tokenizer.Next(); 265ffe3c632Sopenharmony_ci if (token.Type == JsonToken.TokenType.EndArray) 266ffe3c632Sopenharmony_ci { 267ffe3c632Sopenharmony_ci return; 268ffe3c632Sopenharmony_ci } 269ffe3c632Sopenharmony_ci tokenizer.PushBack(token); 270ffe3c632Sopenharmony_ci object value = ParseSingleValue(field, tokenizer); 271ffe3c632Sopenharmony_ci if (value == null) 272ffe3c632Sopenharmony_ci { 273ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Repeated field elements cannot be null"); 274ffe3c632Sopenharmony_ci } 275ffe3c632Sopenharmony_ci list.Add(value); 276ffe3c632Sopenharmony_ci } 277ffe3c632Sopenharmony_ci } 278ffe3c632Sopenharmony_ci 279ffe3c632Sopenharmony_ci private void MergeMapField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer) 280ffe3c632Sopenharmony_ci { 281ffe3c632Sopenharmony_ci // Map fields are always objects, even if the values are well-known types: ParseSingleValue handles those. 282ffe3c632Sopenharmony_ci var token = tokenizer.Next(); 283ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StartObject) 284ffe3c632Sopenharmony_ci { 285ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected an object to populate a map"); 286ffe3c632Sopenharmony_ci } 287ffe3c632Sopenharmony_ci 288ffe3c632Sopenharmony_ci var type = field.MessageType; 289ffe3c632Sopenharmony_ci var keyField = type.FindFieldByNumber(1); 290ffe3c632Sopenharmony_ci var valueField = type.FindFieldByNumber(2); 291ffe3c632Sopenharmony_ci if (keyField == null || valueField == null) 292ffe3c632Sopenharmony_ci { 293ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid map field: " + field.FullName); 294ffe3c632Sopenharmony_ci } 295ffe3c632Sopenharmony_ci IDictionary dictionary = (IDictionary) field.Accessor.GetValue(message); 296ffe3c632Sopenharmony_ci 297ffe3c632Sopenharmony_ci while (true) 298ffe3c632Sopenharmony_ci { 299ffe3c632Sopenharmony_ci token = tokenizer.Next(); 300ffe3c632Sopenharmony_ci if (token.Type == JsonToken.TokenType.EndObject) 301ffe3c632Sopenharmony_ci { 302ffe3c632Sopenharmony_ci return; 303ffe3c632Sopenharmony_ci } 304ffe3c632Sopenharmony_ci object key = ParseMapKey(keyField, token.StringValue); 305ffe3c632Sopenharmony_ci object value = ParseSingleValue(valueField, tokenizer); 306ffe3c632Sopenharmony_ci if (value == null) 307ffe3c632Sopenharmony_ci { 308ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Map values must not be null"); 309ffe3c632Sopenharmony_ci } 310ffe3c632Sopenharmony_ci dictionary[key] = value; 311ffe3c632Sopenharmony_ci } 312ffe3c632Sopenharmony_ci } 313ffe3c632Sopenharmony_ci 314ffe3c632Sopenharmony_ci private static bool IsGoogleProtobufValueField(FieldDescriptor field) 315ffe3c632Sopenharmony_ci { 316ffe3c632Sopenharmony_ci return field.FieldType == FieldType.Message && 317ffe3c632Sopenharmony_ci field.MessageType.FullName == Value.Descriptor.FullName; 318ffe3c632Sopenharmony_ci } 319ffe3c632Sopenharmony_ci 320ffe3c632Sopenharmony_ci private static bool IsGoogleProtobufNullValueField(FieldDescriptor field) 321ffe3c632Sopenharmony_ci { 322ffe3c632Sopenharmony_ci return field.FieldType == FieldType.Enum && 323ffe3c632Sopenharmony_ci field.EnumType.FullName == NullValueDescriptor.FullName; 324ffe3c632Sopenharmony_ci } 325ffe3c632Sopenharmony_ci 326ffe3c632Sopenharmony_ci private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer) 327ffe3c632Sopenharmony_ci { 328ffe3c632Sopenharmony_ci var token = tokenizer.Next(); 329ffe3c632Sopenharmony_ci if (token.Type == JsonToken.TokenType.Null) 330ffe3c632Sopenharmony_ci { 331ffe3c632Sopenharmony_ci // TODO: In order to support dynamic messages, we should really build this up 332ffe3c632Sopenharmony_ci // dynamically. 333ffe3c632Sopenharmony_ci if (IsGoogleProtobufValueField(field)) 334ffe3c632Sopenharmony_ci { 335ffe3c632Sopenharmony_ci return Value.ForNull(); 336ffe3c632Sopenharmony_ci } 337ffe3c632Sopenharmony_ci if (IsGoogleProtobufNullValueField(field)) 338ffe3c632Sopenharmony_ci { 339ffe3c632Sopenharmony_ci return NullValue.NullValue; 340ffe3c632Sopenharmony_ci } 341ffe3c632Sopenharmony_ci return null; 342ffe3c632Sopenharmony_ci } 343ffe3c632Sopenharmony_ci 344ffe3c632Sopenharmony_ci var fieldType = field.FieldType; 345ffe3c632Sopenharmony_ci if (fieldType == FieldType.Message) 346ffe3c632Sopenharmony_ci { 347ffe3c632Sopenharmony_ci // Parse wrapper types as their constituent types. 348ffe3c632Sopenharmony_ci // TODO: What does this mean for null? 349ffe3c632Sopenharmony_ci if (field.MessageType.IsWrapperType) 350ffe3c632Sopenharmony_ci { 351ffe3c632Sopenharmony_ci field = field.MessageType.Fields[WrappersReflection.WrapperValueFieldNumber]; 352ffe3c632Sopenharmony_ci fieldType = field.FieldType; 353ffe3c632Sopenharmony_ci } 354ffe3c632Sopenharmony_ci else 355ffe3c632Sopenharmony_ci { 356ffe3c632Sopenharmony_ci // TODO: Merge the current value in message? (Public API currently doesn't make this relevant as we don't expose merging.) 357ffe3c632Sopenharmony_ci tokenizer.PushBack(token); 358ffe3c632Sopenharmony_ci IMessage subMessage = NewMessageForField(field); 359ffe3c632Sopenharmony_ci Merge(subMessage, tokenizer); 360ffe3c632Sopenharmony_ci return subMessage; 361ffe3c632Sopenharmony_ci } 362ffe3c632Sopenharmony_ci } 363ffe3c632Sopenharmony_ci 364ffe3c632Sopenharmony_ci switch (token.Type) 365ffe3c632Sopenharmony_ci { 366ffe3c632Sopenharmony_ci case JsonToken.TokenType.True: 367ffe3c632Sopenharmony_ci case JsonToken.TokenType.False: 368ffe3c632Sopenharmony_ci if (fieldType == FieldType.Bool) 369ffe3c632Sopenharmony_ci { 370ffe3c632Sopenharmony_ci return token.Type == JsonToken.TokenType.True; 371ffe3c632Sopenharmony_ci } 372ffe3c632Sopenharmony_ci // Fall through to "we don't support this type for this case"; could duplicate the behaviour of the default 373ffe3c632Sopenharmony_ci // case instead, but this way we'd only need to change one place. 374ffe3c632Sopenharmony_ci goto default; 375ffe3c632Sopenharmony_ci case JsonToken.TokenType.StringValue: 376ffe3c632Sopenharmony_ci return ParseSingleStringValue(field, token.StringValue); 377ffe3c632Sopenharmony_ci // Note: not passing the number value itself here, as we may end up storing the string value in the token too. 378ffe3c632Sopenharmony_ci case JsonToken.TokenType.Number: 379ffe3c632Sopenharmony_ci return ParseSingleNumberValue(field, token); 380ffe3c632Sopenharmony_ci case JsonToken.TokenType.Null: 381ffe3c632Sopenharmony_ci throw new NotImplementedException("Haven't worked out what to do for null yet"); 382ffe3c632Sopenharmony_ci default: 383ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Unsupported JSON token type " + token.Type + " for field type " + fieldType); 384ffe3c632Sopenharmony_ci } 385ffe3c632Sopenharmony_ci } 386ffe3c632Sopenharmony_ci 387ffe3c632Sopenharmony_ci /// <summary> 388ffe3c632Sopenharmony_ci /// Parses <paramref name="json"/> into a new message. 389ffe3c632Sopenharmony_ci /// </summary> 390ffe3c632Sopenharmony_ci /// <typeparam name="T">The type of message to create.</typeparam> 391ffe3c632Sopenharmony_ci /// <param name="json">The JSON to parse.</param> 392ffe3c632Sopenharmony_ci /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception> 393ffe3c632Sopenharmony_ci /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception> 394ffe3c632Sopenharmony_ci public T Parse<T>(string json) where T : IMessage, new() 395ffe3c632Sopenharmony_ci { 396ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(json, nameof(json)); 397ffe3c632Sopenharmony_ci return Parse<T>(new StringReader(json)); 398ffe3c632Sopenharmony_ci } 399ffe3c632Sopenharmony_ci 400ffe3c632Sopenharmony_ci /// <summary> 401ffe3c632Sopenharmony_ci /// Parses JSON read from <paramref name="jsonReader"/> into a new message. 402ffe3c632Sopenharmony_ci /// </summary> 403ffe3c632Sopenharmony_ci /// <typeparam name="T">The type of message to create.</typeparam> 404ffe3c632Sopenharmony_ci /// <param name="jsonReader">Reader providing the JSON to parse.</param> 405ffe3c632Sopenharmony_ci /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception> 406ffe3c632Sopenharmony_ci /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception> 407ffe3c632Sopenharmony_ci public T Parse<T>(TextReader jsonReader) where T : IMessage, new() 408ffe3c632Sopenharmony_ci { 409ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader)); 410ffe3c632Sopenharmony_ci T message = new T(); 411ffe3c632Sopenharmony_ci Merge(message, jsonReader); 412ffe3c632Sopenharmony_ci return message; 413ffe3c632Sopenharmony_ci } 414ffe3c632Sopenharmony_ci 415ffe3c632Sopenharmony_ci /// <summary> 416ffe3c632Sopenharmony_ci /// Parses <paramref name="json"/> into a new message. 417ffe3c632Sopenharmony_ci /// </summary> 418ffe3c632Sopenharmony_ci /// <param name="json">The JSON to parse.</param> 419ffe3c632Sopenharmony_ci /// <param name="descriptor">Descriptor of message type to parse.</param> 420ffe3c632Sopenharmony_ci /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception> 421ffe3c632Sopenharmony_ci /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception> 422ffe3c632Sopenharmony_ci public IMessage Parse(string json, MessageDescriptor descriptor) 423ffe3c632Sopenharmony_ci { 424ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(json, nameof(json)); 425ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor)); 426ffe3c632Sopenharmony_ci return Parse(new StringReader(json), descriptor); 427ffe3c632Sopenharmony_ci } 428ffe3c632Sopenharmony_ci 429ffe3c632Sopenharmony_ci /// <summary> 430ffe3c632Sopenharmony_ci /// Parses JSON read from <paramref name="jsonReader"/> into a new message. 431ffe3c632Sopenharmony_ci /// </summary> 432ffe3c632Sopenharmony_ci /// <param name="jsonReader">Reader providing the JSON to parse.</param> 433ffe3c632Sopenharmony_ci /// <param name="descriptor">Descriptor of message type to parse.</param> 434ffe3c632Sopenharmony_ci /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception> 435ffe3c632Sopenharmony_ci /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception> 436ffe3c632Sopenharmony_ci public IMessage Parse(TextReader jsonReader, MessageDescriptor descriptor) 437ffe3c632Sopenharmony_ci { 438ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader)); 439ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor)); 440ffe3c632Sopenharmony_ci IMessage message = descriptor.Parser.CreateTemplate(); 441ffe3c632Sopenharmony_ci Merge(message, jsonReader); 442ffe3c632Sopenharmony_ci return message; 443ffe3c632Sopenharmony_ci } 444ffe3c632Sopenharmony_ci 445ffe3c632Sopenharmony_ci private void MergeStructValue(IMessage message, JsonTokenizer tokenizer) 446ffe3c632Sopenharmony_ci { 447ffe3c632Sopenharmony_ci var firstToken = tokenizer.Next(); 448ffe3c632Sopenharmony_ci var fields = message.Descriptor.Fields; 449ffe3c632Sopenharmony_ci switch (firstToken.Type) 450ffe3c632Sopenharmony_ci { 451ffe3c632Sopenharmony_ci case JsonToken.TokenType.Null: 452ffe3c632Sopenharmony_ci fields[Value.NullValueFieldNumber].Accessor.SetValue(message, 0); 453ffe3c632Sopenharmony_ci return; 454ffe3c632Sopenharmony_ci case JsonToken.TokenType.StringValue: 455ffe3c632Sopenharmony_ci fields[Value.StringValueFieldNumber].Accessor.SetValue(message, firstToken.StringValue); 456ffe3c632Sopenharmony_ci return; 457ffe3c632Sopenharmony_ci case JsonToken.TokenType.Number: 458ffe3c632Sopenharmony_ci fields[Value.NumberValueFieldNumber].Accessor.SetValue(message, firstToken.NumberValue); 459ffe3c632Sopenharmony_ci return; 460ffe3c632Sopenharmony_ci case JsonToken.TokenType.False: 461ffe3c632Sopenharmony_ci case JsonToken.TokenType.True: 462ffe3c632Sopenharmony_ci fields[Value.BoolValueFieldNumber].Accessor.SetValue(message, firstToken.Type == JsonToken.TokenType.True); 463ffe3c632Sopenharmony_ci return; 464ffe3c632Sopenharmony_ci case JsonToken.TokenType.StartObject: 465ffe3c632Sopenharmony_ci { 466ffe3c632Sopenharmony_ci var field = fields[Value.StructValueFieldNumber]; 467ffe3c632Sopenharmony_ci var structMessage = NewMessageForField(field); 468ffe3c632Sopenharmony_ci tokenizer.PushBack(firstToken); 469ffe3c632Sopenharmony_ci Merge(structMessage, tokenizer); 470ffe3c632Sopenharmony_ci field.Accessor.SetValue(message, structMessage); 471ffe3c632Sopenharmony_ci return; 472ffe3c632Sopenharmony_ci } 473ffe3c632Sopenharmony_ci case JsonToken.TokenType.StartArray: 474ffe3c632Sopenharmony_ci { 475ffe3c632Sopenharmony_ci var field = fields[Value.ListValueFieldNumber]; 476ffe3c632Sopenharmony_ci var list = NewMessageForField(field); 477ffe3c632Sopenharmony_ci tokenizer.PushBack(firstToken); 478ffe3c632Sopenharmony_ci Merge(list, tokenizer); 479ffe3c632Sopenharmony_ci field.Accessor.SetValue(message, list); 480ffe3c632Sopenharmony_ci return; 481ffe3c632Sopenharmony_ci } 482ffe3c632Sopenharmony_ci default: 483ffe3c632Sopenharmony_ci throw new InvalidOperationException("Unexpected token type: " + firstToken.Type); 484ffe3c632Sopenharmony_ci } 485ffe3c632Sopenharmony_ci } 486ffe3c632Sopenharmony_ci 487ffe3c632Sopenharmony_ci private void MergeStruct(IMessage message, JsonTokenizer tokenizer) 488ffe3c632Sopenharmony_ci { 489ffe3c632Sopenharmony_ci var token = tokenizer.Next(); 490ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StartObject) 491ffe3c632Sopenharmony_ci { 492ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected object value for Struct"); 493ffe3c632Sopenharmony_ci } 494ffe3c632Sopenharmony_ci tokenizer.PushBack(token); 495ffe3c632Sopenharmony_ci 496ffe3c632Sopenharmony_ci var field = message.Descriptor.Fields[Struct.FieldsFieldNumber]; 497ffe3c632Sopenharmony_ci MergeMapField(message, field, tokenizer); 498ffe3c632Sopenharmony_ci } 499ffe3c632Sopenharmony_ci 500ffe3c632Sopenharmony_ci private void MergeAny(IMessage message, JsonTokenizer tokenizer) 501ffe3c632Sopenharmony_ci { 502ffe3c632Sopenharmony_ci // Record the token stream until we see the @type property. At that point, we can take the value, consult 503ffe3c632Sopenharmony_ci // the type registry for the relevant message, and replay the stream, omitting the @type property. 504ffe3c632Sopenharmony_ci var tokens = new List<JsonToken>(); 505ffe3c632Sopenharmony_ci 506ffe3c632Sopenharmony_ci var token = tokenizer.Next(); 507ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StartObject) 508ffe3c632Sopenharmony_ci { 509ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected object value for Any"); 510ffe3c632Sopenharmony_ci } 511ffe3c632Sopenharmony_ci int typeUrlObjectDepth = tokenizer.ObjectDepth; 512ffe3c632Sopenharmony_ci 513ffe3c632Sopenharmony_ci // The check for the property depth protects us from nested Any values which occur before the type URL 514ffe3c632Sopenharmony_ci // for *this* Any. 515ffe3c632Sopenharmony_ci while (token.Type != JsonToken.TokenType.Name || 516ffe3c632Sopenharmony_ci token.StringValue != JsonFormatter.AnyTypeUrlField || 517ffe3c632Sopenharmony_ci tokenizer.ObjectDepth != typeUrlObjectDepth) 518ffe3c632Sopenharmony_ci { 519ffe3c632Sopenharmony_ci tokens.Add(token); 520ffe3c632Sopenharmony_ci token = tokenizer.Next(); 521ffe3c632Sopenharmony_ci 522ffe3c632Sopenharmony_ci if (tokenizer.ObjectDepth < typeUrlObjectDepth) 523ffe3c632Sopenharmony_ci { 524ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Any message with no @type"); 525ffe3c632Sopenharmony_ci } 526ffe3c632Sopenharmony_ci } 527ffe3c632Sopenharmony_ci 528ffe3c632Sopenharmony_ci // Don't add the @type property or its value to the recorded token list 529ffe3c632Sopenharmony_ci token = tokenizer.Next(); 530ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StringValue) 531ffe3c632Sopenharmony_ci { 532ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected string value for Any.@type"); 533ffe3c632Sopenharmony_ci } 534ffe3c632Sopenharmony_ci string typeUrl = token.StringValue; 535ffe3c632Sopenharmony_ci string typeName = Any.GetTypeName(typeUrl); 536ffe3c632Sopenharmony_ci 537ffe3c632Sopenharmony_ci MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName); 538ffe3c632Sopenharmony_ci if (descriptor == null) 539ffe3c632Sopenharmony_ci { 540ffe3c632Sopenharmony_ci throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'"); 541ffe3c632Sopenharmony_ci } 542ffe3c632Sopenharmony_ci 543ffe3c632Sopenharmony_ci // Now replay the token stream we've already read and anything that remains of the object, just parsing it 544ffe3c632Sopenharmony_ci // as normal. Our original tokenizer should end up at the end of the object. 545ffe3c632Sopenharmony_ci var replay = JsonTokenizer.FromReplayedTokens(tokens, tokenizer); 546ffe3c632Sopenharmony_ci var body = descriptor.Parser.CreateTemplate(); 547ffe3c632Sopenharmony_ci if (descriptor.IsWellKnownType) 548ffe3c632Sopenharmony_ci { 549ffe3c632Sopenharmony_ci MergeWellKnownTypeAnyBody(body, replay); 550ffe3c632Sopenharmony_ci } 551ffe3c632Sopenharmony_ci else 552ffe3c632Sopenharmony_ci { 553ffe3c632Sopenharmony_ci Merge(body, replay); 554ffe3c632Sopenharmony_ci } 555ffe3c632Sopenharmony_ci var data = body.ToByteString(); 556ffe3c632Sopenharmony_ci 557ffe3c632Sopenharmony_ci // Now that we have the message data, we can pack it into an Any (the message received as a parameter). 558ffe3c632Sopenharmony_ci message.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.SetValue(message, typeUrl); 559ffe3c632Sopenharmony_ci message.Descriptor.Fields[Any.ValueFieldNumber].Accessor.SetValue(message, data); 560ffe3c632Sopenharmony_ci } 561ffe3c632Sopenharmony_ci 562ffe3c632Sopenharmony_ci // Well-known types end up in a property called "value" in the JSON. As there's no longer a @type property 563ffe3c632Sopenharmony_ci // in the given JSON token stream, we should *only* have tokens of start-object, name("value"), the value 564ffe3c632Sopenharmony_ci // itself, and then end-object. 565ffe3c632Sopenharmony_ci private void MergeWellKnownTypeAnyBody(IMessage body, JsonTokenizer tokenizer) 566ffe3c632Sopenharmony_ci { 567ffe3c632Sopenharmony_ci var token = tokenizer.Next(); // Definitely start-object; checked in previous method 568ffe3c632Sopenharmony_ci token = tokenizer.Next(); 569ffe3c632Sopenharmony_ci // TODO: What about an absent Int32Value, for example? 570ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.Name || token.StringValue != JsonFormatter.AnyWellKnownTypeValueField) 571ffe3c632Sopenharmony_ci { 572ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Expected '{JsonFormatter.AnyWellKnownTypeValueField}' property for well-known type Any body"); 573ffe3c632Sopenharmony_ci } 574ffe3c632Sopenharmony_ci Merge(body, tokenizer); 575ffe3c632Sopenharmony_ci token = tokenizer.Next(); 576ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.EndObject) 577ffe3c632Sopenharmony_ci { 578ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Expected end-object token after @type/value for well-known type"); 579ffe3c632Sopenharmony_ci } 580ffe3c632Sopenharmony_ci } 581ffe3c632Sopenharmony_ci 582ffe3c632Sopenharmony_ci #region Utility methods which don't depend on the state (or settings) of the parser. 583ffe3c632Sopenharmony_ci private static object ParseMapKey(FieldDescriptor field, string keyText) 584ffe3c632Sopenharmony_ci { 585ffe3c632Sopenharmony_ci switch (field.FieldType) 586ffe3c632Sopenharmony_ci { 587ffe3c632Sopenharmony_ci case FieldType.Bool: 588ffe3c632Sopenharmony_ci if (keyText == "true") 589ffe3c632Sopenharmony_ci { 590ffe3c632Sopenharmony_ci return true; 591ffe3c632Sopenharmony_ci } 592ffe3c632Sopenharmony_ci if (keyText == "false") 593ffe3c632Sopenharmony_ci { 594ffe3c632Sopenharmony_ci return false; 595ffe3c632Sopenharmony_ci } 596ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid string for bool map key: " + keyText); 597ffe3c632Sopenharmony_ci case FieldType.String: 598ffe3c632Sopenharmony_ci return keyText; 599ffe3c632Sopenharmony_ci case FieldType.Int32: 600ffe3c632Sopenharmony_ci case FieldType.SInt32: 601ffe3c632Sopenharmony_ci case FieldType.SFixed32: 602ffe3c632Sopenharmony_ci return ParseNumericString(keyText, int.Parse); 603ffe3c632Sopenharmony_ci case FieldType.UInt32: 604ffe3c632Sopenharmony_ci case FieldType.Fixed32: 605ffe3c632Sopenharmony_ci return ParseNumericString(keyText, uint.Parse); 606ffe3c632Sopenharmony_ci case FieldType.Int64: 607ffe3c632Sopenharmony_ci case FieldType.SInt64: 608ffe3c632Sopenharmony_ci case FieldType.SFixed64: 609ffe3c632Sopenharmony_ci return ParseNumericString(keyText, long.Parse); 610ffe3c632Sopenharmony_ci case FieldType.UInt64: 611ffe3c632Sopenharmony_ci case FieldType.Fixed64: 612ffe3c632Sopenharmony_ci return ParseNumericString(keyText, ulong.Parse); 613ffe3c632Sopenharmony_ci default: 614ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid field type for map: " + field.FieldType); 615ffe3c632Sopenharmony_ci } 616ffe3c632Sopenharmony_ci } 617ffe3c632Sopenharmony_ci 618ffe3c632Sopenharmony_ci private static object ParseSingleNumberValue(FieldDescriptor field, JsonToken token) 619ffe3c632Sopenharmony_ci { 620ffe3c632Sopenharmony_ci double value = token.NumberValue; 621ffe3c632Sopenharmony_ci checked 622ffe3c632Sopenharmony_ci { 623ffe3c632Sopenharmony_ci try 624ffe3c632Sopenharmony_ci { 625ffe3c632Sopenharmony_ci switch (field.FieldType) 626ffe3c632Sopenharmony_ci { 627ffe3c632Sopenharmony_ci case FieldType.Int32: 628ffe3c632Sopenharmony_ci case FieldType.SInt32: 629ffe3c632Sopenharmony_ci case FieldType.SFixed32: 630ffe3c632Sopenharmony_ci CheckInteger(value); 631ffe3c632Sopenharmony_ci return (int) value; 632ffe3c632Sopenharmony_ci case FieldType.UInt32: 633ffe3c632Sopenharmony_ci case FieldType.Fixed32: 634ffe3c632Sopenharmony_ci CheckInteger(value); 635ffe3c632Sopenharmony_ci return (uint) value; 636ffe3c632Sopenharmony_ci case FieldType.Int64: 637ffe3c632Sopenharmony_ci case FieldType.SInt64: 638ffe3c632Sopenharmony_ci case FieldType.SFixed64: 639ffe3c632Sopenharmony_ci CheckInteger(value); 640ffe3c632Sopenharmony_ci return (long) value; 641ffe3c632Sopenharmony_ci case FieldType.UInt64: 642ffe3c632Sopenharmony_ci case FieldType.Fixed64: 643ffe3c632Sopenharmony_ci CheckInteger(value); 644ffe3c632Sopenharmony_ci return (ulong) value; 645ffe3c632Sopenharmony_ci case FieldType.Double: 646ffe3c632Sopenharmony_ci return value; 647ffe3c632Sopenharmony_ci case FieldType.Float: 648ffe3c632Sopenharmony_ci if (double.IsNaN(value)) 649ffe3c632Sopenharmony_ci { 650ffe3c632Sopenharmony_ci return float.NaN; 651ffe3c632Sopenharmony_ci } 652ffe3c632Sopenharmony_ci if (value > float.MaxValue || value < float.MinValue) 653ffe3c632Sopenharmony_ci { 654ffe3c632Sopenharmony_ci if (double.IsPositiveInfinity(value)) 655ffe3c632Sopenharmony_ci { 656ffe3c632Sopenharmony_ci return float.PositiveInfinity; 657ffe3c632Sopenharmony_ci } 658ffe3c632Sopenharmony_ci if (double.IsNegativeInfinity(value)) 659ffe3c632Sopenharmony_ci { 660ffe3c632Sopenharmony_ci return float.NegativeInfinity; 661ffe3c632Sopenharmony_ci } 662ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Value out of range: {value}"); 663ffe3c632Sopenharmony_ci } 664ffe3c632Sopenharmony_ci return (float) value; 665ffe3c632Sopenharmony_ci case FieldType.Enum: 666ffe3c632Sopenharmony_ci CheckInteger(value); 667ffe3c632Sopenharmony_ci // Just return it as an int, and let the CLR convert it. 668ffe3c632Sopenharmony_ci // Note that we deliberately don't check that it's a known value. 669ffe3c632Sopenharmony_ci return (int) value; 670ffe3c632Sopenharmony_ci default: 671ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Unsupported conversion from JSON number for field type {field.FieldType}"); 672ffe3c632Sopenharmony_ci } 673ffe3c632Sopenharmony_ci } 674ffe3c632Sopenharmony_ci catch (OverflowException) 675ffe3c632Sopenharmony_ci { 676ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Value out of range: {value}"); 677ffe3c632Sopenharmony_ci } 678ffe3c632Sopenharmony_ci } 679ffe3c632Sopenharmony_ci } 680ffe3c632Sopenharmony_ci 681ffe3c632Sopenharmony_ci private static void CheckInteger(double value) 682ffe3c632Sopenharmony_ci { 683ffe3c632Sopenharmony_ci if (double.IsInfinity(value) || double.IsNaN(value)) 684ffe3c632Sopenharmony_ci { 685ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Value not an integer: {value}"); 686ffe3c632Sopenharmony_ci } 687ffe3c632Sopenharmony_ci if (value != Math.Floor(value)) 688ffe3c632Sopenharmony_ci { 689ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Value not an integer: {value}"); 690ffe3c632Sopenharmony_ci } 691ffe3c632Sopenharmony_ci } 692ffe3c632Sopenharmony_ci 693ffe3c632Sopenharmony_ci private static object ParseSingleStringValue(FieldDescriptor field, string text) 694ffe3c632Sopenharmony_ci { 695ffe3c632Sopenharmony_ci switch (field.FieldType) 696ffe3c632Sopenharmony_ci { 697ffe3c632Sopenharmony_ci case FieldType.String: 698ffe3c632Sopenharmony_ci return text; 699ffe3c632Sopenharmony_ci case FieldType.Bytes: 700ffe3c632Sopenharmony_ci try 701ffe3c632Sopenharmony_ci { 702ffe3c632Sopenharmony_ci return ByteString.FromBase64(text); 703ffe3c632Sopenharmony_ci } 704ffe3c632Sopenharmony_ci catch (FormatException e) 705ffe3c632Sopenharmony_ci { 706ffe3c632Sopenharmony_ci throw InvalidProtocolBufferException.InvalidBase64(e); 707ffe3c632Sopenharmony_ci } 708ffe3c632Sopenharmony_ci case FieldType.Int32: 709ffe3c632Sopenharmony_ci case FieldType.SInt32: 710ffe3c632Sopenharmony_ci case FieldType.SFixed32: 711ffe3c632Sopenharmony_ci return ParseNumericString(text, int.Parse); 712ffe3c632Sopenharmony_ci case FieldType.UInt32: 713ffe3c632Sopenharmony_ci case FieldType.Fixed32: 714ffe3c632Sopenharmony_ci return ParseNumericString(text, uint.Parse); 715ffe3c632Sopenharmony_ci case FieldType.Int64: 716ffe3c632Sopenharmony_ci case FieldType.SInt64: 717ffe3c632Sopenharmony_ci case FieldType.SFixed64: 718ffe3c632Sopenharmony_ci return ParseNumericString(text, long.Parse); 719ffe3c632Sopenharmony_ci case FieldType.UInt64: 720ffe3c632Sopenharmony_ci case FieldType.Fixed64: 721ffe3c632Sopenharmony_ci return ParseNumericString(text, ulong.Parse); 722ffe3c632Sopenharmony_ci case FieldType.Double: 723ffe3c632Sopenharmony_ci double d = ParseNumericString(text, double.Parse); 724ffe3c632Sopenharmony_ci ValidateInfinityAndNan(text, double.IsPositiveInfinity(d), double.IsNegativeInfinity(d), double.IsNaN(d)); 725ffe3c632Sopenharmony_ci return d; 726ffe3c632Sopenharmony_ci case FieldType.Float: 727ffe3c632Sopenharmony_ci float f = ParseNumericString(text, float.Parse); 728ffe3c632Sopenharmony_ci ValidateInfinityAndNan(text, float.IsPositiveInfinity(f), float.IsNegativeInfinity(f), float.IsNaN(f)); 729ffe3c632Sopenharmony_ci return f; 730ffe3c632Sopenharmony_ci case FieldType.Enum: 731ffe3c632Sopenharmony_ci var enumValue = field.EnumType.FindValueByName(text); 732ffe3c632Sopenharmony_ci if (enumValue == null) 733ffe3c632Sopenharmony_ci { 734ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid enum value: {text} for enum type: {field.EnumType.FullName}"); 735ffe3c632Sopenharmony_ci } 736ffe3c632Sopenharmony_ci // Just return it as an int, and let the CLR convert it. 737ffe3c632Sopenharmony_ci return enumValue.Number; 738ffe3c632Sopenharmony_ci default: 739ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Unsupported conversion from JSON string for field type {field.FieldType}"); 740ffe3c632Sopenharmony_ci } 741ffe3c632Sopenharmony_ci } 742ffe3c632Sopenharmony_ci 743ffe3c632Sopenharmony_ci /// <summary> 744ffe3c632Sopenharmony_ci /// Creates a new instance of the message type for the given field. 745ffe3c632Sopenharmony_ci /// </summary> 746ffe3c632Sopenharmony_ci private static IMessage NewMessageForField(FieldDescriptor field) 747ffe3c632Sopenharmony_ci { 748ffe3c632Sopenharmony_ci return field.MessageType.Parser.CreateTemplate(); 749ffe3c632Sopenharmony_ci } 750ffe3c632Sopenharmony_ci 751ffe3c632Sopenharmony_ci private static T ParseNumericString<T>(string text, Func<string, NumberStyles, IFormatProvider, T> parser) 752ffe3c632Sopenharmony_ci { 753ffe3c632Sopenharmony_ci // Can't prohibit this with NumberStyles. 754ffe3c632Sopenharmony_ci if (text.StartsWith("+")) 755ffe3c632Sopenharmony_ci { 756ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid numeric value: {text}"); 757ffe3c632Sopenharmony_ci } 758ffe3c632Sopenharmony_ci if (text.StartsWith("0") && text.Length > 1) 759ffe3c632Sopenharmony_ci { 760ffe3c632Sopenharmony_ci if (text[1] >= '0' && text[1] <= '9') 761ffe3c632Sopenharmony_ci { 762ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid numeric value: {text}"); 763ffe3c632Sopenharmony_ci } 764ffe3c632Sopenharmony_ci } 765ffe3c632Sopenharmony_ci else if (text.StartsWith("-0") && text.Length > 2) 766ffe3c632Sopenharmony_ci { 767ffe3c632Sopenharmony_ci if (text[2] >= '0' && text[2] <= '9') 768ffe3c632Sopenharmony_ci { 769ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid numeric value: {text}"); 770ffe3c632Sopenharmony_ci } 771ffe3c632Sopenharmony_ci } 772ffe3c632Sopenharmony_ci try 773ffe3c632Sopenharmony_ci { 774ffe3c632Sopenharmony_ci return parser(text, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); 775ffe3c632Sopenharmony_ci } 776ffe3c632Sopenharmony_ci catch (FormatException) 777ffe3c632Sopenharmony_ci { 778ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid numeric value for type: {text}"); 779ffe3c632Sopenharmony_ci } 780ffe3c632Sopenharmony_ci catch (OverflowException) 781ffe3c632Sopenharmony_ci { 782ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Value out of range: {text}"); 783ffe3c632Sopenharmony_ci } 784ffe3c632Sopenharmony_ci } 785ffe3c632Sopenharmony_ci 786ffe3c632Sopenharmony_ci /// <summary> 787ffe3c632Sopenharmony_ci /// Checks that any infinite/NaN values originated from the correct text. 788ffe3c632Sopenharmony_ci /// This corrects the lenient whitespace handling of double.Parse/float.Parse, as well as the 789ffe3c632Sopenharmony_ci /// way that Mono parses out-of-range values as infinity. 790ffe3c632Sopenharmony_ci /// </summary> 791ffe3c632Sopenharmony_ci private static void ValidateInfinityAndNan(string text, bool isPositiveInfinity, bool isNegativeInfinity, bool isNaN) 792ffe3c632Sopenharmony_ci { 793ffe3c632Sopenharmony_ci if ((isPositiveInfinity && text != "Infinity") || 794ffe3c632Sopenharmony_ci (isNegativeInfinity && text != "-Infinity") || 795ffe3c632Sopenharmony_ci (isNaN && text != "NaN")) 796ffe3c632Sopenharmony_ci { 797ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid numeric value: {text}"); 798ffe3c632Sopenharmony_ci } 799ffe3c632Sopenharmony_ci } 800ffe3c632Sopenharmony_ci 801ffe3c632Sopenharmony_ci private static void MergeTimestamp(IMessage message, JsonToken token) 802ffe3c632Sopenharmony_ci { 803ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StringValue) 804ffe3c632Sopenharmony_ci { 805ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected string value for Timestamp"); 806ffe3c632Sopenharmony_ci } 807ffe3c632Sopenharmony_ci var match = TimestampRegex.Match(token.StringValue); 808ffe3c632Sopenharmony_ci if (!match.Success) 809ffe3c632Sopenharmony_ci { 810ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid Timestamp value: {token.StringValue}"); 811ffe3c632Sopenharmony_ci } 812ffe3c632Sopenharmony_ci var dateTime = match.Groups["datetime"].Value; 813ffe3c632Sopenharmony_ci var subseconds = match.Groups["subseconds"].Value; 814ffe3c632Sopenharmony_ci var offset = match.Groups["offset"].Value; 815ffe3c632Sopenharmony_ci 816ffe3c632Sopenharmony_ci try 817ffe3c632Sopenharmony_ci { 818ffe3c632Sopenharmony_ci DateTime parsed = DateTime.ParseExact( 819ffe3c632Sopenharmony_ci dateTime, 820ffe3c632Sopenharmony_ci "yyyy-MM-dd'T'HH:mm:ss", 821ffe3c632Sopenharmony_ci CultureInfo.InvariantCulture, 822ffe3c632Sopenharmony_ci DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); 823ffe3c632Sopenharmony_ci // TODO: It would be nice not to have to create all these objects... easy to optimize later though. 824ffe3c632Sopenharmony_ci Timestamp timestamp = Timestamp.FromDateTime(parsed); 825ffe3c632Sopenharmony_ci int nanosToAdd = 0; 826ffe3c632Sopenharmony_ci if (subseconds != "") 827ffe3c632Sopenharmony_ci { 828ffe3c632Sopenharmony_ci // This should always work, as we've got 1-9 digits. 829ffe3c632Sopenharmony_ci int parsedFraction = int.Parse(subseconds.Substring(1), CultureInfo.InvariantCulture); 830ffe3c632Sopenharmony_ci nanosToAdd = parsedFraction * SubsecondScalingFactors[subseconds.Length]; 831ffe3c632Sopenharmony_ci } 832ffe3c632Sopenharmony_ci int secondsToAdd = 0; 833ffe3c632Sopenharmony_ci if (offset != "Z") 834ffe3c632Sopenharmony_ci { 835ffe3c632Sopenharmony_ci // This is the amount we need to *subtract* from the local time to get to UTC - hence - => +1 and vice versa. 836ffe3c632Sopenharmony_ci int sign = offset[0] == '-' ? 1 : -1; 837ffe3c632Sopenharmony_ci int hours = int.Parse(offset.Substring(1, 2), CultureInfo.InvariantCulture); 838ffe3c632Sopenharmony_ci int minutes = int.Parse(offset.Substring(4, 2)); 839ffe3c632Sopenharmony_ci int totalMinutes = hours * 60 + minutes; 840ffe3c632Sopenharmony_ci if (totalMinutes > 18 * 60) 841ffe3c632Sopenharmony_ci { 842ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue); 843ffe3c632Sopenharmony_ci } 844ffe3c632Sopenharmony_ci if (totalMinutes == 0 && sign == 1) 845ffe3c632Sopenharmony_ci { 846ffe3c632Sopenharmony_ci // This is an offset of -00:00, which means "unknown local offset". It makes no sense for a timestamp. 847ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue); 848ffe3c632Sopenharmony_ci } 849ffe3c632Sopenharmony_ci // We need to *subtract* the offset from local time to get UTC. 850ffe3c632Sopenharmony_ci secondsToAdd = sign * totalMinutes * 60; 851ffe3c632Sopenharmony_ci } 852ffe3c632Sopenharmony_ci // Ensure we've got the right signs. Currently unnecessary, but easy to do. 853ffe3c632Sopenharmony_ci if (secondsToAdd < 0 && nanosToAdd > 0) 854ffe3c632Sopenharmony_ci { 855ffe3c632Sopenharmony_ci secondsToAdd++; 856ffe3c632Sopenharmony_ci nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond; 857ffe3c632Sopenharmony_ci } 858ffe3c632Sopenharmony_ci if (secondsToAdd != 0 || nanosToAdd != 0) 859ffe3c632Sopenharmony_ci { 860ffe3c632Sopenharmony_ci timestamp += new Duration { Nanos = nanosToAdd, Seconds = secondsToAdd }; 861ffe3c632Sopenharmony_ci // The resulting timestamp after offset change would be out of our expected range. Currently the Timestamp message doesn't validate this 862ffe3c632Sopenharmony_ci // anywhere, but we shouldn't parse it. 863ffe3c632Sopenharmony_ci if (timestamp.Seconds < Timestamp.UnixSecondsAtBclMinValue || timestamp.Seconds > Timestamp.UnixSecondsAtBclMaxValue) 864ffe3c632Sopenharmony_ci { 865ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue); 866ffe3c632Sopenharmony_ci } 867ffe3c632Sopenharmony_ci } 868ffe3c632Sopenharmony_ci message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.SetValue(message, timestamp.Seconds); 869ffe3c632Sopenharmony_ci message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.SetValue(message, timestamp.Nanos); 870ffe3c632Sopenharmony_ci } 871ffe3c632Sopenharmony_ci catch (FormatException) 872ffe3c632Sopenharmony_ci { 873ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue); 874ffe3c632Sopenharmony_ci } 875ffe3c632Sopenharmony_ci } 876ffe3c632Sopenharmony_ci 877ffe3c632Sopenharmony_ci private static void MergeDuration(IMessage message, JsonToken token) 878ffe3c632Sopenharmony_ci { 879ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StringValue) 880ffe3c632Sopenharmony_ci { 881ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected string value for Duration"); 882ffe3c632Sopenharmony_ci } 883ffe3c632Sopenharmony_ci var match = DurationRegex.Match(token.StringValue); 884ffe3c632Sopenharmony_ci if (!match.Success) 885ffe3c632Sopenharmony_ci { 886ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue); 887ffe3c632Sopenharmony_ci } 888ffe3c632Sopenharmony_ci var sign = match.Groups["sign"].Value; 889ffe3c632Sopenharmony_ci var secondsText = match.Groups["int"].Value; 890ffe3c632Sopenharmony_ci // Prohibit leading insignficant zeroes 891ffe3c632Sopenharmony_ci if (secondsText[0] == '0' && secondsText.Length > 1) 892ffe3c632Sopenharmony_ci { 893ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue); 894ffe3c632Sopenharmony_ci } 895ffe3c632Sopenharmony_ci var subseconds = match.Groups["subseconds"].Value; 896ffe3c632Sopenharmony_ci var multiplier = sign == "-" ? -1 : 1; 897ffe3c632Sopenharmony_ci 898ffe3c632Sopenharmony_ci try 899ffe3c632Sopenharmony_ci { 900ffe3c632Sopenharmony_ci long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture) * multiplier; 901ffe3c632Sopenharmony_ci int nanos = 0; 902ffe3c632Sopenharmony_ci if (subseconds != "") 903ffe3c632Sopenharmony_ci { 904ffe3c632Sopenharmony_ci // This should always work, as we've got 1-9 digits. 905ffe3c632Sopenharmony_ci int parsedFraction = int.Parse(subseconds.Substring(1)); 906ffe3c632Sopenharmony_ci nanos = parsedFraction * SubsecondScalingFactors[subseconds.Length] * multiplier; 907ffe3c632Sopenharmony_ci } 908ffe3c632Sopenharmony_ci if (!Duration.IsNormalized(seconds, nanos)) 909ffe3c632Sopenharmony_ci { 910ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid Duration value: {token.StringValue}"); 911ffe3c632Sopenharmony_ci } 912ffe3c632Sopenharmony_ci message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds); 913ffe3c632Sopenharmony_ci message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos); 914ffe3c632Sopenharmony_ci } 915ffe3c632Sopenharmony_ci catch (FormatException) 916ffe3c632Sopenharmony_ci { 917ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid Duration value: {token.StringValue}"); 918ffe3c632Sopenharmony_ci } 919ffe3c632Sopenharmony_ci } 920ffe3c632Sopenharmony_ci 921ffe3c632Sopenharmony_ci private static void MergeFieldMask(IMessage message, JsonToken token) 922ffe3c632Sopenharmony_ci { 923ffe3c632Sopenharmony_ci if (token.Type != JsonToken.TokenType.StringValue) 924ffe3c632Sopenharmony_ci { 925ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException("Expected string value for FieldMask"); 926ffe3c632Sopenharmony_ci } 927ffe3c632Sopenharmony_ci // TODO: Do we *want* to remove empty entries? Probably okay to treat "" as "no paths", but "foo,,bar"? 928ffe3c632Sopenharmony_ci string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators, StringSplitOptions.RemoveEmptyEntries); 929ffe3c632Sopenharmony_ci IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(message); 930ffe3c632Sopenharmony_ci foreach (var path in jsonPaths) 931ffe3c632Sopenharmony_ci { 932ffe3c632Sopenharmony_ci messagePaths.Add(ToSnakeCase(path)); 933ffe3c632Sopenharmony_ci } 934ffe3c632Sopenharmony_ci } 935ffe3c632Sopenharmony_ci 936ffe3c632Sopenharmony_ci // Ported from src/google/protobuf/util/internal/utility.cc 937ffe3c632Sopenharmony_ci private static string ToSnakeCase(string text) 938ffe3c632Sopenharmony_ci { 939ffe3c632Sopenharmony_ci var builder = new StringBuilder(text.Length * 2); 940ffe3c632Sopenharmony_ci // Note: this is probably unnecessary now, but currently retained to be as close as possible to the 941ffe3c632Sopenharmony_ci // C++, whilst still throwing an exception on underscores. 942ffe3c632Sopenharmony_ci bool wasNotUnderscore = false; // Initialize to false for case 1 (below) 943ffe3c632Sopenharmony_ci bool wasNotCap = false; 944ffe3c632Sopenharmony_ci 945ffe3c632Sopenharmony_ci for (int i = 0; i < text.Length; i++) 946ffe3c632Sopenharmony_ci { 947ffe3c632Sopenharmony_ci char c = text[i]; 948ffe3c632Sopenharmony_ci if (c >= 'A' && c <= 'Z') // ascii_isupper 949ffe3c632Sopenharmony_ci { 950ffe3c632Sopenharmony_ci // Consider when the current character B is capitalized: 951ffe3c632Sopenharmony_ci // 1) At beginning of input: "B..." => "b..." 952ffe3c632Sopenharmony_ci // (e.g. "Biscuit" => "biscuit") 953ffe3c632Sopenharmony_ci // 2) Following a lowercase: "...aB..." => "...a_b..." 954ffe3c632Sopenharmony_ci // (e.g. "gBike" => "g_bike") 955ffe3c632Sopenharmony_ci // 3) At the end of input: "...AB" => "...ab" 956ffe3c632Sopenharmony_ci // (e.g. "GoogleLAB" => "google_lab") 957ffe3c632Sopenharmony_ci // 4) Followed by a lowercase: "...ABc..." => "...a_bc..." 958ffe3c632Sopenharmony_ci // (e.g. "GBike" => "g_bike") 959ffe3c632Sopenharmony_ci if (wasNotUnderscore && // case 1 out 960ffe3c632Sopenharmony_ci (wasNotCap || // case 2 in, case 3 out 961ffe3c632Sopenharmony_ci (i + 1 < text.Length && // case 3 out 962ffe3c632Sopenharmony_ci (text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii_islower(text[i + 1]) 963ffe3c632Sopenharmony_ci { // case 4 in 964ffe3c632Sopenharmony_ci // We add an underscore for case 2 and case 4. 965ffe3c632Sopenharmony_ci builder.Append('_'); 966ffe3c632Sopenharmony_ci } 967ffe3c632Sopenharmony_ci // ascii_tolower, but we already know that c *is* an upper case ASCII character... 968ffe3c632Sopenharmony_ci builder.Append((char) (c + 'a' - 'A')); 969ffe3c632Sopenharmony_ci wasNotUnderscore = true; 970ffe3c632Sopenharmony_ci wasNotCap = false; 971ffe3c632Sopenharmony_ci } 972ffe3c632Sopenharmony_ci else 973ffe3c632Sopenharmony_ci { 974ffe3c632Sopenharmony_ci builder.Append(c); 975ffe3c632Sopenharmony_ci if (c == '_') 976ffe3c632Sopenharmony_ci { 977ffe3c632Sopenharmony_ci throw new InvalidProtocolBufferException($"Invalid field mask: {text}"); 978ffe3c632Sopenharmony_ci } 979ffe3c632Sopenharmony_ci wasNotUnderscore = true; 980ffe3c632Sopenharmony_ci wasNotCap = true; 981ffe3c632Sopenharmony_ci } 982ffe3c632Sopenharmony_ci } 983ffe3c632Sopenharmony_ci return builder.ToString(); 984ffe3c632Sopenharmony_ci } 985ffe3c632Sopenharmony_ci #endregion 986ffe3c632Sopenharmony_ci 987ffe3c632Sopenharmony_ci /// <summary> 988ffe3c632Sopenharmony_ci /// Settings controlling JSON parsing. 989ffe3c632Sopenharmony_ci /// </summary> 990ffe3c632Sopenharmony_ci public sealed class Settings 991ffe3c632Sopenharmony_ci { 992ffe3c632Sopenharmony_ci /// <summary> 993ffe3c632Sopenharmony_ci /// Default settings, as used by <see cref="JsonParser.Default"/>. This has the same default 994ffe3c632Sopenharmony_ci /// recursion limit as <see cref="CodedInputStream"/>, and an empty type registry. 995ffe3c632Sopenharmony_ci /// </summary> 996ffe3c632Sopenharmony_ci public static Settings Default { get; } 997ffe3c632Sopenharmony_ci 998ffe3c632Sopenharmony_ci // Workaround for the Mono compiler complaining about XML comments not being on 999ffe3c632Sopenharmony_ci // valid language elements. 1000ffe3c632Sopenharmony_ci static Settings() 1001ffe3c632Sopenharmony_ci { 1002ffe3c632Sopenharmony_ci Default = new Settings(CodedInputStream.DefaultRecursionLimit); 1003ffe3c632Sopenharmony_ci } 1004ffe3c632Sopenharmony_ci 1005ffe3c632Sopenharmony_ci /// <summary> 1006ffe3c632Sopenharmony_ci /// The maximum depth of messages to parse. Note that this limit only applies to parsing 1007ffe3c632Sopenharmony_ci /// messages, not collections - so a message within a collection within a message only counts as 1008ffe3c632Sopenharmony_ci /// depth 2, not 3. 1009ffe3c632Sopenharmony_ci /// </summary> 1010ffe3c632Sopenharmony_ci public int RecursionLimit { get; } 1011ffe3c632Sopenharmony_ci 1012ffe3c632Sopenharmony_ci /// <summary> 1013ffe3c632Sopenharmony_ci /// The type registry used to parse <see cref="Any"/> messages. 1014ffe3c632Sopenharmony_ci /// </summary> 1015ffe3c632Sopenharmony_ci public TypeRegistry TypeRegistry { get; } 1016ffe3c632Sopenharmony_ci 1017ffe3c632Sopenharmony_ci /// <summary> 1018ffe3c632Sopenharmony_ci /// Whether the parser should ignore unknown fields (<c>true</c>) or throw an exception when 1019ffe3c632Sopenharmony_ci /// they are encountered (<c>false</c>). 1020ffe3c632Sopenharmony_ci /// </summary> 1021ffe3c632Sopenharmony_ci public bool IgnoreUnknownFields { get; } 1022ffe3c632Sopenharmony_ci 1023ffe3c632Sopenharmony_ci private Settings(int recursionLimit, TypeRegistry typeRegistry, bool ignoreUnknownFields) 1024ffe3c632Sopenharmony_ci { 1025ffe3c632Sopenharmony_ci RecursionLimit = recursionLimit; 1026ffe3c632Sopenharmony_ci TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry)); 1027ffe3c632Sopenharmony_ci IgnoreUnknownFields = ignoreUnknownFields; 1028ffe3c632Sopenharmony_ci } 1029ffe3c632Sopenharmony_ci 1030ffe3c632Sopenharmony_ci /// <summary> 1031ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified recursion limit. 1032ffe3c632Sopenharmony_ci /// </summary> 1033ffe3c632Sopenharmony_ci /// <param name="recursionLimit">The maximum depth of messages to parse</param> 1034ffe3c632Sopenharmony_ci public Settings(int recursionLimit) : this(recursionLimit, TypeRegistry.Empty) 1035ffe3c632Sopenharmony_ci { 1036ffe3c632Sopenharmony_ci } 1037ffe3c632Sopenharmony_ci 1038ffe3c632Sopenharmony_ci /// <summary> 1039ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object with the specified recursion limit and type registry. 1040ffe3c632Sopenharmony_ci /// </summary> 1041ffe3c632Sopenharmony_ci /// <param name="recursionLimit">The maximum depth of messages to parse</param> 1042ffe3c632Sopenharmony_ci /// <param name="typeRegistry">The type registry used to parse <see cref="Any"/> messages</param> 1043ffe3c632Sopenharmony_ci public Settings(int recursionLimit, TypeRegistry typeRegistry) : this(recursionLimit, typeRegistry, false) 1044ffe3c632Sopenharmony_ci { 1045ffe3c632Sopenharmony_ci } 1046ffe3c632Sopenharmony_ci 1047ffe3c632Sopenharmony_ci /// <summary> 1048ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object set to either ignore unknown fields, or throw an exception 1049ffe3c632Sopenharmony_ci /// when unknown fields are encountered. 1050ffe3c632Sopenharmony_ci /// </summary> 1051ffe3c632Sopenharmony_ci /// <param name="ignoreUnknownFields"><c>true</c> if unknown fields should be ignored when parsing; <c>false</c> to throw an exception.</param> 1052ffe3c632Sopenharmony_ci public Settings WithIgnoreUnknownFields(bool ignoreUnknownFields) => 1053ffe3c632Sopenharmony_ci new Settings(RecursionLimit, TypeRegistry, ignoreUnknownFields); 1054ffe3c632Sopenharmony_ci 1055ffe3c632Sopenharmony_ci /// <summary> 1056ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object based on this one, but with the specified recursion limit. 1057ffe3c632Sopenharmony_ci /// </summary> 1058ffe3c632Sopenharmony_ci /// <param name="recursionLimit">The new recursion limit.</param> 1059ffe3c632Sopenharmony_ci public Settings WithRecursionLimit(int recursionLimit) => 1060ffe3c632Sopenharmony_ci new Settings(recursionLimit, TypeRegistry, IgnoreUnknownFields); 1061ffe3c632Sopenharmony_ci 1062ffe3c632Sopenharmony_ci /// <summary> 1063ffe3c632Sopenharmony_ci /// Creates a new <see cref="Settings"/> object based on this one, but with the specified type registry. 1064ffe3c632Sopenharmony_ci /// </summary> 1065ffe3c632Sopenharmony_ci /// <param name="typeRegistry">The new type registry. Must not be null.</param> 1066ffe3c632Sopenharmony_ci public Settings WithTypeRegistry(TypeRegistry typeRegistry) => 1067ffe3c632Sopenharmony_ci new Settings( 1068ffe3c632Sopenharmony_ci RecursionLimit, 1069ffe3c632Sopenharmony_ci ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry)), 1070ffe3c632Sopenharmony_ci IgnoreUnknownFields); 1071ffe3c632Sopenharmony_ci } 1072ffe3c632Sopenharmony_ci } 1073ffe3c632Sopenharmony_ci} 1074