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