Esempio n. 1
0
        public bool TryGetParameter(
            ReadOnlySpan <byte> propertyName,
            ref ReadStackFrame frame,
            out JsonParameterInfo?jsonParameterInfo)
        {
            JsonParameterInfo?info = null;

            // Keep a local copy of the cache in case it changes by another thread.
            ParameterRef[]? localParameterRefsSorted = _parameterRefsSorted;

            ulong key = GetKey(propertyName);

            // If there is an existing cache, then use it.
            if (localParameterRefsSorted != null)
            {
                // Start with the current parameter index, and then go forwards\backwards.
                int parameterIndex = frame.CtorArgumentState !.ParameterIndex;

                int count     = localParameterRefsSorted.Length;
                int iForward  = Math.Min(parameterIndex, count);
                int iBackward = iForward - 1;

                while (true)
                {
                    if (iForward < count)
                    {
                        ParameterRef parameterRef = localParameterRefsSorted[iForward];
                        if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info))
                        {
                            jsonParameterInfo = info;
                            return(true);
                        }

                        ++iForward;

                        if (iBackward >= 0)
                        {
                            parameterRef = localParameterRefsSorted[iBackward];
                            if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info))
                            {
                                jsonParameterInfo = info;
                                return(true);
                            }

                            --iBackward;
                        }
                    }
                    else if (iBackward >= 0)
                    {
                        ParameterRef parameterRef = localParameterRefsSorted[iBackward];
                        if (TryIsParameterRefEqual(parameterRef, propertyName, key, ref info))
                        {
                            jsonParameterInfo = info;
                            return(true);
                        }

                        --iBackward;
                    }
                    else
                    {
                        // Property was not found.
                        break;
                    }
                }
            }

            string propertyNameAsString = JsonHelpers.Utf8GetString(propertyName);

            Debug.Assert(ParameterCache != null);

            if (!ParameterCache.TryGetValue(propertyNameAsString, out info))
            {
                // Constructor parameter not found. We'll check if it's a property next.
                jsonParameterInfo = null;
                return(false);
            }

            jsonParameterInfo = info;
            Debug.Assert(info != null);

            // Two code paths to get here:
            // 1) key == info.PropertyNameKey. Exact match found.
            // 2) key != info.PropertyNameKey. Match found due to case insensitivity.
            // TODO: recheck these conditions
            Debug.Assert(key == info.ParameterNameKey ||
                         propertyNameAsString.Equals(info.NameAsString, StringComparison.OrdinalIgnoreCase));

            // Check if we should add this to the cache.
            // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache.
            int cacheCount = 0;

            if (localParameterRefsSorted != null)
            {
                cacheCount = localParameterRefsSorted.Length;
            }

            // Do a quick check for the stable (after warm-up) case.
            if (cacheCount < ParameterNameCountCacheThreshold)
            {
                // Do a slower check for the warm-up case.
                if (frame.CtorArgumentState !.ParameterRefCache != null)
                {
                    cacheCount += frame.CtorArgumentState.ParameterRefCache.Count;
                }

                // Check again to append the cache up to the threshold.
                if (cacheCount < ParameterNameCountCacheThreshold)
                {
                    if (frame.CtorArgumentState.ParameterRefCache == null)
                    {
                        frame.CtorArgumentState.ParameterRefCache = new List <ParameterRef>();
                    }

                    ParameterRef parameterRef = new ParameterRef(key, jsonParameterInfo);
                    frame.CtorArgumentState.ParameterRefCache.Add(parameterRef);
                }
            }

            return(true);
        }
Esempio n. 2
0
        /// <summary>
        ///   Parses a string representing JSON document into <see cref="JsonNode"/>.
        /// </summary>
        /// <param name="json">JSON to parse.</param>
        /// <param name="options">Options to control the parsing behavior.</param>
        /// <returns><see cref="JsonNode"/> representation of <paramref name="json"/>.</returns>
        public static JsonNode Parse(string json, JsonNodeOptions options = default)
        {
            Utf8JsonReader reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), options.GetReaderOptions());

            var      currentNodes = new Stack <KeyValuePair <string?, JsonNode?> >(); // nodes currently being created
            JsonNode?toReturn     = null;

            while (reader.Read())
            {
                JsonTokenType tokenType = reader.TokenType;
                currentNodes.TryPeek(out KeyValuePair <string?, JsonNode?> currentPair);

                void AddNewPair(JsonNode jsonNode, bool keepInCurrentNodes = false)
                {
                    KeyValuePair <string?, JsonNode?> newProperty;

                    if (currentPair.Value == null)
                    {
                        // If previous token was property name,
                        // it was added to stack with not null name and null value,
                        // otherwise, this is first JsonNode added
                        if (currentPair.Key != null)
                        {
                            // Create as property, keep name, replace null with new JsonNode:
                            currentNodes.Pop();
                            newProperty = new KeyValuePair <string?, JsonNode?>(currentPair.Key, jsonNode);
                        }
                        else
                        {
                            // Add first JsonNode:
                            newProperty = new KeyValuePair <string?, JsonNode?>(null, jsonNode);
                        }
                    }
                    else
                    {
                        // Create as value:
                        newProperty = new KeyValuePair <string?, JsonNode?>(null, jsonNode);
                    }

                    if (keepInCurrentNodes)
                    {
                        // If after adding property, it should be kept in currentNodes, it must be JsonObject or JsonArray
                        Debug.Assert(jsonNode.ValueKind == JsonValueKind.Object || jsonNode.ValueKind == JsonValueKind.Array);

                        currentNodes.Push(newProperty);
                    }
                    else
                    {
                        AddToParent(newProperty, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling);
                    }
                }

                switch (tokenType)
                {
                case JsonTokenType.StartObject:
                    AddNewPair(new JsonObject(), true);
                    break;

                case JsonTokenType.EndObject:
                    Debug.Assert(currentPair.Value is JsonObject);

                    currentNodes.Pop();
                    AddToParent(currentPair, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling);
                    break;

                case JsonTokenType.StartArray:
                    AddNewPair(new JsonArray(), true);
                    break;

                case JsonTokenType.EndArray:
                    Debug.Assert(currentPair.Value is JsonArray);

                    currentNodes.Pop();
                    AddToParent(currentPair, ref currentNodes, ref toReturn, options.DuplicatePropertyNameHandling);
                    break;

                case JsonTokenType.PropertyName:
                    currentNodes.Push(new KeyValuePair <string?, JsonNode?>(reader.GetString(), null));
                    break;

                case JsonTokenType.Number:
                    AddNewPair(new JsonNumber(JsonHelpers.Utf8GetString(reader.ValueSpan)));
                    break;

                case JsonTokenType.String:
                    AddNewPair(new JsonString(reader.GetString() !));
                    break;

                case JsonTokenType.True:
                    AddNewPair(new JsonBoolean(true));
                    break;

                case JsonTokenType.False:
                    AddNewPair(new JsonBoolean(false));
                    break;

                case JsonTokenType.Null:
                    AddNewPair(JsonNull.Instance);
                    break;
                }
            }

            Debug.Assert(toReturn != null);
            return(toReturn);
        }
Esempio n. 3
0
        public T?SetValue(string propertyName, T?value, Action?assignParent = null)
        {
            if (IsReadOnly)
            {
                ThrowHelper.ThrowNotSupportedException_NodeCollectionIsReadOnly();
            }

            if (propertyName == null)
            {
                throw new ArgumentNullException(nameof(propertyName));
            }

            CreateDictionaryIfThresholdMet();

            T?existing = null;

            if (_propertyDictionary != null)
            {
                // Fast path if item doesn't exist in dictionary.
                if (JsonHelpers.TryAdd(_propertyDictionary, propertyName, value))
                {
                    assignParent?.Invoke();
                    _propertyList.Add(new KeyValuePair <string, T?>(propertyName, value));
                    return(null);
                }

                existing = _propertyDictionary[propertyName];
                if (ReferenceEquals(existing, value))
                {
                    // Ignore if the same value.
                    return(null);
                }
            }

            int i = FindValueIndex(propertyName);

            if (i >= 0)
            {
                if (_propertyDictionary != null)
                {
                    _propertyDictionary[propertyName] = value;
                }
                else
                {
                    KeyValuePair <string, T?> current = _propertyList[i];
                    if (ReferenceEquals(current.Value, value))
                    {
                        // Ignore if the same value.
                        return(null);
                    }

                    existing = current.Value;
                }

                assignParent?.Invoke();
                _propertyList[i] = new KeyValuePair <string, T?>(propertyName, value);
            }
            else
            {
                assignParent?.Invoke();
                _propertyDictionary?.Add(propertyName, value);
                _propertyList.Add(new KeyValuePair <string, T?>(propertyName, value));
                Debug.Assert(existing == null);
            }

            return(existing);
        }
Esempio n. 4
0
        public JsonPropertyInfo GetProperty(ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame)
        {
            JsonPropertyInfo?info = null;

            // Keep a local copy of the cache in case it changes by another thread.
            PropertyRef[]? localPropertyRefsSorted = _propertyRefsSorted;

            ulong key = GetKey(propertyName);

            // If there is an existing cache, then use it.
            if (localPropertyRefsSorted != null)
            {
                // Start with the current property index, and then go forwards\backwards.
                int propertyIndex = frame.PropertyIndex;

                int count     = localPropertyRefsSorted.Length;
                int iForward  = Math.Min(propertyIndex, count);
                int iBackward = iForward - 1;

                while (true)
                {
                    if (iForward < count)
                    {
                        PropertyRef propertyRef = localPropertyRefsSorted[iForward];
                        if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
                        {
                            return(info);
                        }

                        ++iForward;

                        if (iBackward >= 0)
                        {
                            propertyRef = localPropertyRefsSorted[iBackward];
                            if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
                            {
                                return(info);
                            }

                            --iBackward;
                        }
                    }
                    else if (iBackward >= 0)
                    {
                        PropertyRef propertyRef = localPropertyRefsSorted[iBackward];
                        if (TryIsPropertyRefEqual(propertyRef, propertyName, key, ref info))
                        {
                            return(info);
                        }

                        --iBackward;
                    }
                    else
                    {
                        // Property was not found.
                        break;
                    }
                }
            }

            // No cached item was found. Try the main list which has all of the properties.

            string stringPropertyName = JsonHelpers.Utf8GetString(propertyName);

            Debug.Assert(PropertyCache != null);

            if (!PropertyCache.TryGetValue(stringPropertyName, out info))
            {
                info = JsonPropertyInfo.s_missingProperty;
            }

            Debug.Assert(info != null);

            // Three code paths to get here:
            // 1) info == s_missingProperty. Property not found.
            // 2) key == info.PropertyNameKey. Exact match found.
            // 3) key != info.PropertyNameKey. Match found due to case insensitivity.
            Debug.Assert(info == JsonPropertyInfo.s_missingProperty || key == info.PropertyNameKey || Options.PropertyNameCaseInsensitive);

            // Check if we should add this to the cache.
            // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache.
            int cacheCount = 0;

            if (localPropertyRefsSorted != null)
            {
                cacheCount = localPropertyRefsSorted.Length;
            }

            // Do a quick check for the stable (after warm-up) case.
            if (cacheCount < PropertyNameCountCacheThreshold)
            {
                // Do a slower check for the warm-up case.
                if (frame.PropertyRefCache != null)
                {
                    cacheCount += frame.PropertyRefCache.Count;
                }

                // Check again to append the cache up to the threshold.
                if (cacheCount < PropertyNameCountCacheThreshold)
                {
                    if (frame.PropertyRefCache == null)
                    {
                        frame.PropertyRefCache = new List <PropertyRef>();
                    }

                    PropertyRef propertyRef = new PropertyRef(key, info);
                    frame.PropertyRefCache.Add(propertyRef);
                }
            }

            return(info);
        }
        internal static void ValidateNumber(ReadOnlySpan <byte> utf8FormattedNumber)
        {
            // This is a simplified version of the number reader from Utf8JsonReader.TryGetNumber,
            // because it doesn't need to deal with "NeedsMoreData", or remembering the format.
            if (utf8FormattedNumber.IsEmpty)
            {
                throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
            }

            int i = 0;

            if (utf8FormattedNumber[i] == '-')
            {
                i++;

                if (utf8FormattedNumber.Length <= i)
                {
                    throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
                }
            }

            if (utf8FormattedNumber[i] == '0')
            {
                i++;
            }
            else
            {
                while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
                {
                    i++;
                }
            }

            if (i == utf8FormattedNumber.Length)
            {
                return;
            }

            byte val = utf8FormattedNumber[i];

            if (val == '.')
            {
                i++;
            }
            else if (val == 'e' || val == 'E')
            {
                i++;

                if (i >= utf8FormattedNumber.Length)
                {
                    throw new ArgumentException(
                              SR.RequiredDigitNotFoundEndOfData,
                              nameof(utf8FormattedNumber));
                }

                val = utf8FormattedNumber[i];

                if (val == '+' || val == '-')
                {
                    i++;
                }
            }
            else
            {
                throw new ArgumentException(
                          SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(val)),
                          nameof(utf8FormattedNumber));
            }

            if (i >= utf8FormattedNumber.Length)
            {
                throw new ArgumentException(
                          SR.RequiredDigitNotFoundEndOfData,
                          nameof(utf8FormattedNumber));
            }

            while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
            {
                i++;
            }

            if (i != utf8FormattedNumber.Length)
            {
                throw new ArgumentException(
                          SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(utf8FormattedNumber[i])),
                          nameof(utf8FormattedNumber));
            }
        }
Esempio n. 6
0
        public JsonParameterInfo?GetParameter(
            ReadOnlySpan <byte> propertyName,
            ref ReadStackFrame frame,
            out byte[] utf8PropertyName)
        {
            ParameterRef parameterRef;

            ulong key = GetKey(propertyName);

            // Keep a local copy of the cache in case it changes by another thread.
            ParameterRef[]? localParameterRefsSorted = _parameterRefsSorted;

            // If there is an existing cache, then use it.
            if (localParameterRefsSorted != null)
            {
                // Start with the current parameter index, and then go forwards\backwards.
                int parameterIndex = frame.CtorArgumentState !.ParameterIndex;

                int count     = localParameterRefsSorted.Length;
                int iForward  = Math.Min(parameterIndex, count);
                int iBackward = iForward - 1;

                while (true)
                {
                    if (iForward < count)
                    {
                        parameterRef = localParameterRefsSorted[iForward];
                        if (IsParameterRefEqual(parameterRef, propertyName, key))
                        {
                            utf8PropertyName = parameterRef.NameFromJson;
                            return(parameterRef.Info);
                        }

                        ++iForward;

                        if (iBackward >= 0)
                        {
                            parameterRef = localParameterRefsSorted[iBackward];
                            if (IsParameterRefEqual(parameterRef, propertyName, key))
                            {
                                utf8PropertyName = parameterRef.NameFromJson;
                                return(parameterRef.Info);
                            }

                            --iBackward;
                        }
                    }
                    else if (iBackward >= 0)
                    {
                        parameterRef = localParameterRefsSorted[iBackward];
                        if (IsParameterRefEqual(parameterRef, propertyName, key))
                        {
                            utf8PropertyName = parameterRef.NameFromJson;
                            return(parameterRef.Info);
                        }

                        --iBackward;
                    }
                    else
                    {
                        // Property was not found.
                        break;
                    }
                }
            }

            // No cached item was found. Try the main dictionary which has all of the parameters.
            Debug.Assert(ParameterCache != null);

            if (ParameterCache.TryGetValue(JsonHelpers.Utf8GetString(propertyName), out JsonParameterInfo? info))
            {
                if (Options.PropertyNameCaseInsensitive)
                {
                    if (propertyName.SequenceEqual(info.NameAsUtf8Bytes))
                    {
                        Debug.Assert(key == GetKey(info.NameAsUtf8Bytes.AsSpan()));

                        // Use the existing byte[] reference instead of creating another one.
                        utf8PropertyName = info.NameAsUtf8Bytes !;
                    }
                    else
                    {
                        // Make a copy of the original Span.
                        utf8PropertyName = propertyName.ToArray();
                    }
                }
                else
                {
                    Debug.Assert(key == GetKey(info.NameAsUtf8Bytes !.AsSpan()));
                    utf8PropertyName = info.NameAsUtf8Bytes !;
                }
            }
            else
            {
                Debug.Assert(info == null);

                // Make a copy of the original Span.
                utf8PropertyName = propertyName.ToArray();
            }

            // Check if we should add this to the cache.
            // Only cache up to a threshold length and then just use the dictionary when an item is not found in the cache.
            int cacheCount = 0;

            if (localParameterRefsSorted != null)
            {
                cacheCount = localParameterRefsSorted.Length;
            }

            // Do a quick check for the stable (after warm-up) case.
            if (cacheCount < ParameterNameCountCacheThreshold)
            {
                // Do a slower check for the warm-up case.
                if (frame.CtorArgumentState !.ParameterRefCache != null)
                {
                    cacheCount += frame.CtorArgumentState.ParameterRefCache.Count;
                }

                // Check again to append the cache up to the threshold.
                if (cacheCount < ParameterNameCountCacheThreshold)
                {
                    if (frame.CtorArgumentState.ParameterRefCache == null)
                    {
                        frame.CtorArgumentState.ParameterRefCache = new List <ParameterRef>();
                    }

                    parameterRef = new ParameterRef(key, info !, utf8PropertyName);
                    frame.CtorArgumentState.ParameterRefCache.Add(parameterRef);
                }
            }

            return(info);
        }
Esempio n. 7
0
        public JsonClassInfo(Type type, JsonSerializerOptions options)
        {
            Type    = type;
            Options = options;

            JsonConverter converter = GetConverter(
                Type,
                parentClassType: null, // A ClassInfo never has a "parent" class.
                propertyInfo: null,    // A ClassInfo never has a "parent" property.
                out Type runtimeType,
                Options);

            ClassType = converter.ClassType;
            PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options);

            switch (ClassType)
            {
            case ClassType.Object:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
                Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>(
                    Options.PropertyNameCaseInsensitive
                                ? StringComparer.OrdinalIgnoreCase
                                : StringComparer.Ordinal);

                for (Type?currentType = type; currentType != null; currentType = currentType.BaseType)
                {
                    foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
                    {
                        // Ignore indexers
                        if (propertyInfo.GetIndexParameters().Length > 0)
                        {
                            continue;
                        }

                        if (IsNonPublicProperty(propertyInfo))
                        {
                            if (JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(propertyInfo) != null)
                            {
                                ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType);
                            }

                            // Non-public properties should not be included for (de)serialization.
                            continue;
                        }

                        // For now we only support public getters\setters
                        if (propertyInfo.GetMethod?.IsPublic == true ||
                            propertyInfo.SetMethod?.IsPublic == true)
                        {
                            JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options);
                            Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null);

                            // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
                            if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
                            {
                                JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];

                                if (other.ShouldDeserialize == false && other.ShouldSerialize == false)
                                {
                                    // Overwrite the one just added since it has [JsonIgnore].
                                    cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
                                }
                                else if (other.PropertyInfo?.Name != jsonPropertyInfo.PropertyInfo?.Name &&
                                         (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true))
                                {
                                    // Check for name equality is required to determine when a new slot is used for the member.
                                    // Therefore, if names are not the same, there is conflict due to the name policy or attributes.
                                    ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo);
                                }
                                // else ignore jsonPropertyInfo since it has [JsonIgnore] or it's hidden by a new slot.
                            }
                        }
                    }
                }

                JsonPropertyInfo[] cacheArray;
                if (DetermineExtensionDataProperty(cache))
                {
                    // Remove from cache since it is handled independently.
                    cache.Remove(DataExtensionProperty !.NameAsString !);

                    cacheArray = new JsonPropertyInfo[cache.Count + 1];

                    // Set the last element to the extension property.
                    cacheArray[cache.Count] = DataExtensionProperty;
                }
                else
                {
                    cacheArray = new JsonPropertyInfo[cache.Count];
                }

                // Set fields when finished to avoid concurrency issues.
                PropertyCache = cache;
                cache.Values.CopyTo(cacheArray, 0);
                PropertyCacheArray = cacheArray;

                if (converter.ConstructorIsParameterized)
                {
                    InitializeConstructorParameters(converter.ConstructorInfo !);
                }
            }
            break;

            case ClassType.Enumerable:
            case ClassType.Dictionary:
            {
                ElementType  = converter.ElementType;
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType);
            }
            break;

            case ClassType.Value:
            case ClassType.NewValue:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
            }
            break;

            case ClassType.None:
            {
                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
            }
            break;

            default:
                Debug.Fail($"Unexpected class type: {ClassType}");
                throw new InvalidOperationException();
            }
        }
Esempio n. 8
0
        public JsonClassInfo(Type type, JsonSerializerOptions options)
        {
            Type      = type;
            Options   = options;
            ClassType = GetClassType(type, options);

            CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);

            // Ignore properties on enumerable.
            switch (ClassType)
            {
            case ClassType.Object:
            {
                PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length);

                foreach (PropertyInfo propertyInfo in properties)
                {
                    // Ignore indexers
                    if (propertyInfo.GetIndexParameters().Length > 0)
                    {
                        continue;
                    }

                    // For now we only support public getters\setters
                    if (propertyInfo.GetMethod?.IsPublic == true ||
                        propertyInfo.SetMethod?.IsPublic == true)
                    {
                        JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
                        Debug.Assert(jsonPropertyInfo != null);

                        // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
                        if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
                        {
                            JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];

                            if (other.ShouldDeserialize == false && other.ShouldSerialize == false)
                            {
                                // Overwrite the one just added since it has [JsonIgnore].
                                cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
                            }
                            else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true)
                            {
                                ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo);
                            }
                            // else ignore jsonPropertyInfo since it has [JsonIgnore].
                        }
                    }
                }

                if (DetermineExtensionDataProperty(cache))
                {
                    // Remove from cache since it is handled independently.
                    cache.Remove(DataExtensionProperty.NameAsString);
                }

                // Set as a unit to avoid concurrency issues.
                PropertyCache = cache;
            }
            break;

            case ClassType.Enumerable:
            case ClassType.Dictionary:
            {
                // Add a single property that maps to the class type so we can have policies applied.
                AddPolicyProperty(type, options);

                Type objectType;
                if (IsNativelySupportedCollection(type))
                {
                    // Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary).
                    objectType = PolicyProperty.RuntimePropertyType;
                }
                else
                {
                    // We need to create the declared instance for types implementing natively supported collections.
                    objectType = PolicyProperty.DeclaredPropertyType;
                }

                CreateObject = options.MemberAccessorStrategy.CreateConstructor(objectType);

                ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options);
            }
            break;

            case ClassType.IDictionaryConstructible:
            {
                // Add a single property that maps to the class type so we can have policies applied.
                AddPolicyProperty(type, options);

                ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options);

                CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor(
                    typeof(Dictionary <,>).MakeGenericType(typeof(string), ElementType));

                CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.DeclaredPropertyType);
            }
            break;

            case ClassType.Value:
                // Add a single property that maps to the class type so we can have policies applied.
                AddPolicyProperty(type, options);
                break;

            case ClassType.Unknown:
                // Add a single property that maps to the class type so we can have policies applied.
                AddPolicyProperty(type, options);
                PropertyCache = new Dictionary <string, JsonPropertyInfo>();
                break;

            default:
                Debug.Fail($"Unexpected class type: {ClassType}");
                break;
            }
        }
        // We can't use the type Rune since it is not available on netstandard2.0
        // To avoid extensive ifdefs and for simplicity, just using an int to reprepsent the scalar value, instead.
        public static SequenceValidity PeekFirstSequence(ReadOnlySpan <byte> data, out int numBytesConsumed, out int rune)
        {
            // This method is implemented to match the behavior of System.Text.Encoding.UTF8 in terms of
            // how many bytes it consumes when reporting invalid sequences. The behavior is as follows:
            //
            // - Some bytes are *always* invalid (ranges [ C0..C1 ] and [ F5..FF ]), and when these
            //   are encountered it's an invalid sequence of length 1.
            //
            // - Multi-byte sequences which are overlong are reported as an invalid sequence of length 2,
            //   since per the Unicode Standard Table 3-7 it's always possible to tell these by the second byte.
            //   Exception: Sequences which begin with [ C0..C1 ] are covered by the above case, thus length 1.
            //
            // - Multi-byte sequences which are improperly terminated (no continuation byte when one is
            //   expected) are reported as invalid sequences up to and including the last seen continuation byte.

            Debug.Assert(JsonHelpers.IsValidUnicodeScalar(ReplacementChar));
            rune = ReplacementChar;

            if (data.IsEmpty)
            {
                // No data to peek at
                numBytesConsumed = 0;
                return(SequenceValidity.Empty);
            }

            byte firstByte = data[0];

            if (IsAsciiValue(firstByte))
            {
                // ASCII byte = well-formed one-byte sequence.
                Debug.Assert(JsonHelpers.IsValidUnicodeScalar(firstByte));
                rune             = firstByte;
                numBytesConsumed = 1;
                return(SequenceValidity.WellFormed);
            }

            if (!JsonHelpers.IsInRangeInclusive(firstByte, (byte)0xC2U, (byte)0xF4U))
            {
                // Standalone continuation byte or "always invalid" byte = ill-formed one-byte sequence.
                goto InvalidOneByteSequence;
            }

            // At this point, we know we're working with a multi-byte sequence,
            // and we know that at least the first byte is potentially valid.

            if (data.Length < 2)
            {
                // One byte of an incomplete multi-byte sequence.
                goto OneByteOfIncompleteMultiByteSequence;
            }

            byte secondByte = data[1];

            if (!IsUtf8ContinuationByte(secondByte))
            {
                // One byte of an improperly terminated multi-byte sequence.
                goto InvalidOneByteSequence;
            }

            if (firstByte < (byte)0xE0U)
            {
                // Well-formed two-byte sequence.
                uint scalar = (((uint)firstByte & 0x1FU) << 6) | ((uint)secondByte & 0x3FU);
                Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar));
                rune             = (int)scalar;
                numBytesConsumed = 2;
                return(SequenceValidity.WellFormed);
            }

            if (firstByte < (byte)0xF0U)
            {
                // Start of a three-byte sequence.
                // Need to check for overlong or surrogate sequences.

                uint scalar = (((uint)firstByte & 0x0FU) << 12) | (((uint)secondByte & 0x3FU) << 6);
                if (scalar < 0x800U || IsLowWordSurrogate(scalar))
                {
                    goto OverlongOutOfRangeOrSurrogateSequence;
                }

                // At this point, we have a valid two-byte start of a three-byte sequence.

                if (data.Length < 3)
                {
                    // Two bytes of an incomplete three-byte sequence.
                    goto TwoBytesOfIncompleteMultiByteSequence;
                }
                else
                {
                    byte thirdByte = data[2];
                    if (IsUtf8ContinuationByte(thirdByte))
                    {
                        // Well-formed three-byte sequence.
                        scalar |= (uint)thirdByte & 0x3FU;
                        Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar));
                        rune             = (int)scalar;
                        numBytesConsumed = 3;
                        return(SequenceValidity.WellFormed);
                    }
                    else
                    {
                        // Two bytes of improperly terminated multi-byte sequence.
                        goto InvalidTwoByteSequence;
                    }
                }
            }

            {
                // Start of four-byte sequence.
                // Need to check for overlong or out-of-range sequences.

                uint scalar = (((uint)firstByte & 0x07U) << 18) | (((uint)secondByte & 0x3FU) << 12);
                Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar));
                if (!JsonHelpers.IsInRangeInclusive(scalar, 0x10000U, 0x10FFFFU))
                {
                    goto OverlongOutOfRangeOrSurrogateSequence;
                }

                // At this point, we have a valid two-byte start of a four-byte sequence.

                if (data.Length < 3)
                {
                    // Two bytes of an incomplete four-byte sequence.
                    goto TwoBytesOfIncompleteMultiByteSequence;
                }
                else
                {
                    byte thirdByte = data[2];
                    if (IsUtf8ContinuationByte(thirdByte))
                    {
                        // Valid three-byte start of a four-byte sequence.

                        if (data.Length < 4)
                        {
                            // Three bytes of an incomplete four-byte sequence.
                            goto ThreeBytesOfIncompleteMultiByteSequence;
                        }
                        else
                        {
                            byte fourthByte = data[3];
                            if (IsUtf8ContinuationByte(fourthByte))
                            {
                                // Well-formed four-byte sequence.
                                scalar |= (((uint)thirdByte & 0x3FU) << 6) | ((uint)fourthByte & 0x3FU);
                                Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar));
                                rune             = (int)scalar;
                                numBytesConsumed = 4;
                                return(SequenceValidity.WellFormed);
                            }
                            else
                            {
                                // Three bytes of an improperly terminated multi-byte sequence.
                                goto InvalidThreeByteSequence;
                            }
                        }
                    }
                    else
                    {
                        // Two bytes of improperly terminated multi-byte sequence.
                        goto InvalidTwoByteSequence;
                    }
                }
            }

            // Everything below here is error handling.

InvalidOneByteSequence:
            numBytesConsumed = 1;
            return(SequenceValidity.Invalid);

InvalidTwoByteSequence:
OverlongOutOfRangeOrSurrogateSequence:
            numBytesConsumed = 2;
            return(SequenceValidity.Invalid);

InvalidThreeByteSequence:
            numBytesConsumed = 3;
            return(SequenceValidity.Invalid);

OneByteOfIncompleteMultiByteSequence:
            numBytesConsumed = 1;
            return(SequenceValidity.Incomplete);

TwoBytesOfIncompleteMultiByteSequence:
            numBytesConsumed = 2;
            return(SequenceValidity.Incomplete);

ThreeBytesOfIncompleteMultiByteSequence:
            numBytesConsumed = 3;
            return(SequenceValidity.Incomplete);
        }
Esempio n. 10
0
        internal static JsonPropertyInfo LookupProperty(
            object obj,
            ref Utf8JsonReader reader,
            JsonSerializerOptions options,
            ref ReadStack state,
            out bool useExtensionProperty,
            bool createExtensionProperty = true)
        {
            Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);

            ReadOnlySpan <byte> unescapedPropertyName = GetPropertyName(ref state, ref reader, options);

            JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current);

            // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called.
            state.Current.PropertyIndex++;

            // Determine if we should use the extension property.
            if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
            {
                JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty;
                if (dataExtProperty != null)
                {
                    state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);

                    if (createExtensionProperty)
                    {
                        CreateDataExtensionProperty(obj, dataExtProperty);
                    }

                    jsonPropertyInfo     = dataExtProperty;
                    useExtensionProperty = true;
                }
                else
                {
                    useExtensionProperty = false;
                }

                state.Current.JsonPropertyInfo = jsonPropertyInfo;
                return(jsonPropertyInfo);
            }

            // Support JsonException.Path.
            Debug.Assert(
                jsonPropertyInfo.JsonPropertyName == null ||
                options.PropertyNameCaseInsensitive ||
                unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));

            state.Current.JsonPropertyInfo = jsonPropertyInfo;

            if (jsonPropertyInfo.JsonPropertyName == null)
            {
                byte[] propertyNameArray = unescapedPropertyName.ToArray();
                if (options.PropertyNameCaseInsensitive)
                {
                    // Each payload can have a different name here; remember the value on the temporary stack.
                    state.Current.JsonPropertyName = propertyNameArray;
                }
                else
                {
                    // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName
                    // so it will match the incoming payload except when case insensitivity is enabled (which is handled above).
                    state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray;
                }
            }

            state.Current.JsonPropertyInfo = jsonPropertyInfo;
            useExtensionProperty           = false;
            return(jsonPropertyInfo);
        }
Esempio n. 11
0
        private static void EscapeNextChars(ReadOnlySpan <char> value, int firstChar, Span <char> destination, ref int consumed, ref int written)
        {
            int nextChar = -1;

            if (JsonHelpers.IsInRangeInclusive(firstChar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
            {
                consumed++;
                if (value.Length <= consumed || firstChar >= JsonConstants.LowSurrogateStartValue)
                {
                    ThrowHelper.ThrowArgumentException_InvalidUTF16(firstChar);
                }

                nextChar = value[consumed];
                if (!JsonHelpers.IsInRangeInclusive(nextChar, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
                {
                    ThrowHelper.ThrowArgumentException_InvalidUTF16(nextChar);
                }
            }

            destination[written++] = '\\';
            switch (firstChar)
            {
            case JsonConstants.LineFeed:
                destination[written++] = 'n';
                break;

            case JsonConstants.CarriageReturn:
                destination[written++] = 'r';
                break;

            case JsonConstants.Tab:
                destination[written++] = 't';
                break;

            case JsonConstants.BackSlash:
                destination[written++] = '\\';
                break;

            case JsonConstants.BackSpace:
                destination[written++] = 'b';
                break;

            case JsonConstants.FormFeed:
                destination[written++] = 'f';
                break;

            default:
                destination[written++] = 'u';
#if BUILDING_INBOX_LIBRARY
                firstChar.TryFormat(destination.Slice(written), out int charsWritten, HexFormatString);
                Debug.Assert(charsWritten == 4);
                written += charsWritten;
#else
                written = WriteHex(firstChar, destination, written);
#endif
                if (nextChar != -1)
                {
                    destination[written++] = '\\';
                    destination[written++] = 'u';
#if BUILDING_INBOX_LIBRARY
                    nextChar.TryFormat(destination.Slice(written), out charsWritten, HexFormatString);
                    Debug.Assert(charsWritten == 4);
                    written += charsWritten;
#else
                    written = WriteHex(nextChar, destination, written);
#endif
                }
                break;
            }
        }
Esempio n. 12
0
        public JsonClassInfo(Type type, JsonSerializerOptions options)
        {
            Type    = type;
            Options = options;

            JsonConverter converter = GetConverter(
                Type,
                parentClassType: null, // A ClassInfo never has a "parent" class.
                propertyInfo: null,    // A ClassInfo never has a "parent" property.
                out Type runtimeType,
                Options);

            ClassType = converter.ClassType;
            PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options);

            switch (ClassType)
            {
            case ClassType.Object:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
                Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>(
                    Options.PropertyNameCaseInsensitive
                                ? StringComparer.OrdinalIgnoreCase
                                : StringComparer.Ordinal);

                HashSet <string>?ignoredProperties = null;

                // We start from the most derived type and ascend to the base type.
                for (Type?currentType = type; currentType != null; currentType = currentType.BaseType)
                {
                    foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
                    {
                        // Ignore indexers
                        if (propertyInfo.GetIndexParameters().Length > 0)
                        {
                            continue;
                        }

                        // For now we only support public properties (i.e. setter and/or getter is public).
                        if (propertyInfo.GetMethod?.IsPublic == true ||
                            propertyInfo.SetMethod?.IsPublic == true)
                        {
                            JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options);
                            Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null);

                            string propertyName = propertyInfo.Name;

                            // The JsonPropertyNameAttribute or naming policy resulted in a collision.
                            if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
                            {
                                JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];

                                if (other.IsIgnored)
                                {
                                    // Overwrite previously cached property since it has [JsonIgnore].
                                    cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
                                }
                                else if (
                                    // Does the current property have `JsonIgnoreAttribute`?
                                    !jsonPropertyInfo.IsIgnored &&
                                    // Is the current property hidden by the previously cached property
                                    // (with `new` keyword, or by overriding)?
                                    other.PropertyInfo !.Name != propertyName &&
                                    // Was a property with the same CLR name was ignored? That property hid the current property,
                                    // thus, if it was ignored, the current property should be ignored too.
                                    ignoredProperties?.Contains(propertyName) != true)
                                {
                                    // We throw if we have two public properties that have the same JSON property name, and neither have been ignored.
                                    ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo);
                                }
                                // Ignore the current property.
                            }

                            if (jsonPropertyInfo.IsIgnored)
                            {
                                (ignoredProperties ??= new HashSet <string>()).Add(propertyName);
                            }
                        }
                        else
                        {
                            if (JsonPropertyInfo.GetAttribute <JsonIncludeAttribute>(propertyInfo) != null)
                            {
                                ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType);
                            }

                            // Non-public properties should not be included for (de)serialization.
                        }
                    }
                }

                JsonPropertyInfo[] cacheArray;
                if (DetermineExtensionDataProperty(cache))
                {
                    // Remove from cache since it is handled independently.
                    cache.Remove(DataExtensionProperty !.NameAsString !);

                    cacheArray = new JsonPropertyInfo[cache.Count + 1];

                    // Set the last element to the extension property.
                    cacheArray[cache.Count] = DataExtensionProperty;
                }
                else
                {
                    cacheArray = new JsonPropertyInfo[cache.Count];
                }

                // Copy the dictionary cache to the array cache.
                cache.Values.CopyTo(cacheArray, 0);

                // Set the array cache field at this point since it is completely initialized.
                // It can now be safely accessed by other threads.
                PropertyCacheArray = cacheArray;

                // Allow constructor parameter logic to remove items from the dictionary since the JSON
                // property values will be passed to the constructor and do not call a property setter.
                if (converter.ConstructorIsParameterized)
                {
                    InitializeConstructorParameters(cache, converter.ConstructorInfo !);
                }

                // Set the dictionary cache field at this point since it is completely initialized.
                // It can now be safely accessed by other threads.
                PropertyCache = cache;
            }
            break;

            case ClassType.Enumerable:
            case ClassType.Dictionary:
            {
                ElementType  = converter.ElementType;
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType);
            }
            break;

            case ClassType.Value:
            case ClassType.NewValue:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
            }
            break;

            case ClassType.None:
            {
                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
            }
            break;

            default:
                Debug.Fail($"Unexpected class type: {ClassType}");
                throw new InvalidOperationException();
            }
        }
Esempio n. 13
0
        private static void ReadCore(
            JsonSerializerOptions options,
            ref Utf8JsonReader reader,
            ref ReadStack readStack)
        {
            try
            {
                JsonReaderState initialState         = default;
                long            initialBytesConsumed = default;

                while (true)
                {
                    if (readStack.ReadAhead)
                    {
                        // When we're reading ahead we always have to save the state
                        // as we don't know if the next token is an opening object or
                        // array brace.
                        initialState         = reader.CurrentState;
                        initialBytesConsumed = reader.BytesConsumed;
                    }

                    if (!reader.Read())
                    {
                        // Need more data
                        break;
                    }

                    JsonTokenType tokenType = reader.TokenType;

                    if (JsonHelpers.IsInRangeInclusive(tokenType, JsonTokenType.String, JsonTokenType.False))
                    {
                        Debug.Assert(tokenType == JsonTokenType.String || tokenType == JsonTokenType.Number || tokenType == JsonTokenType.True || tokenType == JsonTokenType.False);

                        HandleValue(tokenType, options, ref reader, ref readStack);
                    }
                    else if (tokenType == JsonTokenType.PropertyName)
                    {
                        HandlePropertyName(options, ref reader, ref readStack);
                    }
                    else if (tokenType == JsonTokenType.StartObject)
                    {
                        if (readStack.Current.SkipProperty)
                        {
                            readStack.Push();
                            readStack.Current.Drain = true;
                        }
                        else if (readStack.Current.IsProcessingValue())
                        {
                            if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed))
                            {
                                // Need more data
                                break;
                            }
                        }
                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible)
                        {
                            HandleStartDictionary(options, ref reader, ref readStack);
                        }
                        else
                        {
                            HandleStartObject(options, ref readStack);
                        }
                    }
                    else if (tokenType == JsonTokenType.EndObject)
                    {
                        if (readStack.Current.Drain)
                        {
                            readStack.Pop();

                            // Clear the current property in case it is a dictionary, since dictionaries must have EndProperty() called when completed.
                            // A non-dictionary property can also have EndProperty() called when completed, although it is redundant.
                            readStack.Current.EndProperty();
                        }
                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible)
                        {
                            HandleEndDictionary(options, ref reader, ref readStack);
                        }
                        else
                        {
                            HandleEndObject(ref reader, ref readStack);
                        }
                    }
                    else if (tokenType == JsonTokenType.StartArray)
                    {
                        if (!readStack.Current.IsProcessingValue())
                        {
                            HandleStartArray(options, ref reader, ref readStack);
                        }
                        else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed))
                        {
                            // Need more data
                            break;
                        }
                    }
                    else if (tokenType == JsonTokenType.EndArray)
                    {
                        HandleEndArray(options, ref reader, ref readStack);
                    }
                    else if (tokenType == JsonTokenType.Null)
                    {
                        HandleNull(ref reader, ref readStack);
                    }
                }
            }
            catch (JsonReaderException ex)
            {
                // Re-throw with Path information.
                ThrowHelper.ReThrowWithPath(readStack, ex);
            }
            catch (FormatException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException)
            {
                ThrowHelper.ReThrowWithPath(readStack, reader, ex);
            }
            catch (InvalidOperationException ex) when(ex.Source == ThrowHelper.ExceptionSourceValueToRethrowAsJsonException)
            {
                ThrowHelper.ReThrowWithPath(readStack, reader, ex);
            }
            catch (JsonException ex)
            {
                ThrowHelper.AddExceptionInformation(readStack, reader, ex);
                throw;
            }

            readStack.BytesConsumed += reader.BytesConsumed;
            return;
        }
Esempio n. 14
0
        internal static JsonPropertyInfo LookupProperty(
            object obj,
            ref Utf8JsonReader reader,
            JsonSerializerOptions options,
            ref ReadStack state,
            out bool useExtensionProperty)
        {
            Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);

            JsonPropertyInfo jsonPropertyInfo;

            ReadOnlySpan <byte> unescapedPropertyName;
            ReadOnlySpan <byte> propertyName = reader.GetSpan();

            if (reader._stringHasEscaping)
            {
                int idx = propertyName.IndexOf(JsonConstants.BackSlash);
                Debug.Assert(idx != -1);
                unescapedPropertyName = JsonReaderHelper.GetUnescapedSpan(propertyName, idx);
            }
            else
            {
                unescapedPropertyName = propertyName;
            }

            if (options.ReferenceHandling.ShouldReadPreservedReferences())
            {
                if (propertyName.Length > 0 && propertyName[0] == '$')
                {
                    ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
                }
            }

            jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(unescapedPropertyName, ref state.Current);

            // Increment PropertyIndex so GetProperty() starts with the next property the next time this function is called.
            state.Current.PropertyIndex++;

            // Determine if we should use the extension property.
            if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
            {
                JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty;
                if (dataExtProperty != null)
                {
                    state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);
                    CreateDataExtensionProperty(obj, dataExtProperty);
                    jsonPropertyInfo = dataExtProperty;
                }

                state.Current.JsonPropertyInfo = jsonPropertyInfo;
                useExtensionProperty           = true;
                return(jsonPropertyInfo);
            }

            // Support JsonException.Path.
            Debug.Assert(
                jsonPropertyInfo.JsonPropertyName == null ||
                options.PropertyNameCaseInsensitive ||
                unescapedPropertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));

            state.Current.JsonPropertyInfo = jsonPropertyInfo;

            if (jsonPropertyInfo.JsonPropertyName == null)
            {
                byte[] propertyNameArray = unescapedPropertyName.ToArray();
                if (options.PropertyNameCaseInsensitive)
                {
                    // Each payload can have a different name here; remember the value on the temporary stack.
                    state.Current.JsonPropertyName = propertyNameArray;
                }
                else
                {
                    // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName
                    // so it will match the incoming payload except when case insensitivity is enabled (which is handled above).
                    state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray;
                }
            }

            state.Current.JsonPropertyInfo = jsonPropertyInfo;
            useExtensionProperty           = false;
            return(jsonPropertyInfo);
        }
        internal static void Unescape(ReadOnlySpan <byte> source, Span <byte> destination, int idx, out int written)
        {
            Debug.Assert(idx >= 0 && idx < source.Length);
            Debug.Assert(source[idx] == JsonConstants.BackSlash);
            Debug.Assert(destination.Length >= source.Length);

            source.Slice(0, idx).CopyTo(destination);
            written = idx;

            for (; idx < source.Length; idx++)
            {
                byte currentByte = source[idx];
                if (currentByte == JsonConstants.BackSlash)
                {
                    idx++;
                    currentByte = source[idx];

                    if (currentByte == JsonConstants.Quote)
                    {
                        destination[written++] = JsonConstants.Quote;
                    }
                    else if (currentByte == 'n')
                    {
                        destination[written++] = JsonConstants.LineFeed;
                    }
                    else if (currentByte == 'r')
                    {
                        destination[written++] = JsonConstants.CarriageReturn;
                    }
                    else if (currentByte == JsonConstants.BackSlash)
                    {
                        destination[written++] = JsonConstants.BackSlash;
                    }
                    else if (currentByte == JsonConstants.Slash)
                    {
                        destination[written++] = JsonConstants.Slash;
                    }
                    else if (currentByte == 't')
                    {
                        destination[written++] = JsonConstants.Tab;
                    }
                    else if (currentByte == 'b')
                    {
                        destination[written++] = JsonConstants.BackSpace;
                    }
                    else if (currentByte == 'f')
                    {
                        destination[written++] = JsonConstants.FormFeed;
                    }
                    else if (currentByte == 'u')
                    {
                        // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
                        // Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
                        Debug.Assert(source.Length >= idx + 5);

                        bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x');
                        Debug.Assert(result);
                        Debug.Assert(bytesConsumed == 4);
                        idx += bytesConsumed;     // The loop iteration will increment idx past the last hex digit

                        if (JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
                        {
                            // The first hex value cannot be a low surrogate.
                            if (scalar >= JsonConstants.LowSurrogateStartValue)
                            {
                                ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(scalar);
                            }

                            Debug.Assert(JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.HighSurrogateEndValue));

                            idx += 3;   // Skip the last hex digit and the next \u

                            // We must have a low surrogate following a high surrogate.
                            if (source.Length < idx + 4 || source[idx - 2] != '\\' || source[idx - 1] != 'u')
                            {
                                ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16();
                            }

                            // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
                            // Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
                            result = Utf8Parser.TryParse(source.Slice(idx, 4), out int lowSurrogate, out bytesConsumed, 'x');
                            Debug.Assert(result);
                            Debug.Assert(bytesConsumed == 4);

                            // If the first hex value is a high surrogate, the next one must be a low surrogate.
                            if (!JsonHelpers.IsInRangeInclusive((uint)lowSurrogate, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
                            {
                                ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(lowSurrogate);
                            }

                            idx += bytesConsumed - 1;  // The loop iteration will increment idx past the last hex digit

                            // To find the unicode scalar:
                            // (0x400 * (High surrogate - 0xD800)) + Low surrogate - 0xDC00 + 0x10000
                            scalar = (JsonConstants.BitShiftBy10 * (scalar - JsonConstants.HighSurrogateStartValue))
                                     + (lowSurrogate - JsonConstants.LowSurrogateStartValue)
                                     + JsonConstants.UnicodePlane01StartValue;
                        }

#if BUILDING_INBOX_LIBRARY
                        var rune = new Rune(scalar);
                        result = rune.TryEncodeToUtf8Bytes(destination.Slice(written), out int bytesWritten);
                        Debug.Assert(result);
#else
                        EncodeToUtf8Bytes((uint)scalar, destination.Slice(written), out int bytesWritten);
#endif
                        Debug.Assert(bytesWritten <= 4);
                        written += bytesWritten;
                    }
                }
                else
                {
                    destination[written++] = currentByte;
                }
            }
        }
Esempio n. 16
0
        // TODO: Replace this with publicly shipping implementation: https://github.com/dotnet/runtime/issues/28204
        /// <summary>
        /// Converts a span containing a sequence of UTF-16 bytes into UTF-8 bytes.
        ///
        /// This method will consume as many of the input bytes as possible.
        ///
        /// On successful exit, the entire input was consumed and encoded successfully. In this case, <paramref name="bytesConsumed"/> will be
        /// equal to the length of the <paramref name="utf16Source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to
        /// the <paramref name="utf8Destination"/>.
        /// </summary>
        /// <param name="utf16Source">A span containing a sequence of UTF-16 bytes.</param>
        /// <param name="utf8Destination">A span to write the UTF-8 bytes into.</param>
        /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="utf16Source"/>.</param>
        /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="utf8Destination"/></param>
        /// <returns>A <see cref="OperationStatus"/> value representing the state of the conversion.</returns>
        public static unsafe OperationStatus ToUtf8(ReadOnlySpan <byte> utf16Source, Span <byte> utf8Destination, out int bytesConsumed, out int bytesWritten)
        {
            //
            //
            // KEEP THIS IMPLEMENTATION IN SYNC WITH https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs#L841
            //
            //
            fixed(byte *chars = &MemoryMarshal.GetReference(utf16Source))
            fixed(byte *bytes = &MemoryMarshal.GetReference(utf8Destination))
            {
                char *pSrc    = (char *)chars;
                byte *pTarget = bytes;

                char *pEnd = (char *)(chars + utf16Source.Length);
                byte *pAllocatedBufferEnd = pTarget + utf8Destination.Length;

                // assume that JIT will enregister pSrc, pTarget and ch

                // Entering the fast encoding loop incurs some overhead that does not get amortized for small
                // number of characters, and the slow encoding loop typically ends up running for the last few
                // characters anyway since the fast encoding loop needs 5 characters on input at least.
                // Thus don't use the fast decoding loop at all if we don't have enough characters. The threashold
                // was chosen based on performance testing.
                // Note that if we don't have enough bytes, pStop will prevent us from entering the fast loop.
                while (pEnd - pSrc > 13)
                {
                    // we need at least 1 byte per character, but Convert might allow us to convert
                    // only part of the input, so try as much as we can.  Reduce charCount if necessary
                    int available = Math.Min(PtrDiff(pEnd, pSrc), PtrDiff(pAllocatedBufferEnd, pTarget));

                    // FASTLOOP:
                    // - optimistic range checks
                    // - fallbacks to the slow loop for all special cases, exception throwing, etc.

                    // To compute the upper bound, assume that all characters are ASCII characters at this point,
                    //  the boundary will be decreased for every non-ASCII character we encounter
                    // Also, we need 5 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates
                    // If there aren't enough bytes for the output, then pStop will be <= pSrc and will bypass the loop.
                    char *pStop = pSrc + available - 5;
                    if (pSrc >= pStop)
                    {
                        break;
                    }

                    do
                    {
                        int ch = *pSrc;
                        pSrc++;

                        if (ch > 0x7F)
                        {
                            goto LongCode;
                        }
                        *pTarget = (byte)ch;
                        pTarget++;

                        // get pSrc aligned
                        if ((unchecked ((int)pSrc) & 0x2) != 0)
                        {
                            ch = *pSrc;
                            pSrc++;
                            if (ch > 0x7F)
                            {
                                goto LongCode;
                            }
                            *pTarget = (byte)ch;
                            pTarget++;
                        }

                        // Run 4 characters at a time!
                        while (pSrc < pStop)
                        {
                            ch = *(int *)pSrc;
                            int chc = *(int *)(pSrc + 2);
                            if (((ch | chc) & unchecked ((int)0xFF80FF80)) != 0)
                            {
                                goto LongCodeWithMask;
                            }

                            // Unfortunately, this is endianness sensitive
                            if (!BitConverter.IsLittleEndian)
                            {
                                *pTarget = (byte)(ch >> 16);
                                *(pTarget + 1) = (byte)ch;
                                pSrc          += 4;
                                *(pTarget + 2) = (byte)(chc >> 16);
                                *(pTarget + 3) = (byte)chc;
                                pTarget       += 4;
                            }
                            else
                            {
                                *pTarget = (byte)ch;
                                *(pTarget + 1) = (byte)(ch >> 16);
                                pSrc          += 4;
                                *(pTarget + 2) = (byte)chc;
                                *(pTarget + 3) = (byte)(chc >> 16);
                                pTarget       += 4;
                            }
                        }
                        continue;

LongCodeWithMask:
                        if (!BitConverter.IsLittleEndian)
                        {
                            // be careful about the sign extension
                            ch = (int)(((uint)ch) >> 16);
                        }
                        else
                        {
                            ch = (char)ch;
                        }
                        pSrc++;

                        if (ch > 0x7F)
                        {
                            goto LongCode;
                        }
                        *pTarget = (byte)ch;
                        pTarget++;
                        continue;

LongCode:
                        // use separate helper variables for slow and fast loop so that the jit optimizations
                        // won't get confused about the variable lifetimes
                        int chd;
                        if (ch <= 0x7FF)
                        {
                            // 2 byte encoding
                            chd = unchecked ((sbyte)0xC0) | (ch >> 6);
                        }
                        else
                        {
                            // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch))
                            if (!JsonHelpers.IsInRangeInclusive(ch, JsonConstants.HighSurrogateStart, JsonConstants.LowSurrogateEnd))
                            {
                                // 3 byte encoding
                                chd = unchecked ((sbyte)0xE0) | (ch >> 12);
                            }
                            else
                            {
                                // 4 byte encoding - high surrogate + low surrogate
                                // if (!IsHighSurrogate(ch))
                                if (ch > JsonConstants.HighSurrogateEnd)
                                {
                                    // low without high -> bad
                                    goto InvalidData;
                                }

                                chd = *pSrc;

                                // if (!IsLowSurrogate(chd)) {
                                if (!JsonHelpers.IsInRangeInclusive(chd, JsonConstants.LowSurrogateStart, JsonConstants.LowSurrogateEnd))
                                {
                                    // high not followed by low -> bad
                                    goto InvalidData;
                                }

                                pSrc++;

                                ch = chd + (ch << 10) +
                                     (0x10000
                                      - JsonConstants.LowSurrogateStart
                                      - (JsonConstants.HighSurrogateStart << 10));

                                *pTarget = (byte)(unchecked ((sbyte)0xF0) | (ch >> 18));
                                // pStop - this byte is compensated by the second surrogate character
                                // 2 input chars require 4 output bytes.  2 have been anticipated already
                                // and 2 more will be accounted for by the 2 pStop-- calls below.
                                pTarget++;

                                chd = unchecked ((sbyte)0x80) | (ch >> 12) & 0x3F;
                            }
                            *pTarget = (byte)chd;
                            pStop--;                    // 3 byte sequence for 1 char, so need pStop-- and the one below too.
                            pTarget++;

                            chd = unchecked ((sbyte)0x80) | (ch >> 6) & 0x3F;
                        }
                        *pTarget = (byte)chd;
                        pStop--;                        // 2 byte sequence for 1 char so need pStop--.

                        *(pTarget + 1) = (byte)(unchecked ((sbyte)0x80) | ch & 0x3F);
                        // pStop - this byte is already included

                        pTarget += 2;
                    }while (pSrc < pStop);

                    Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetBytes]pTarget <= pAllocatedBufferEnd");
                }

                while (pSrc < pEnd)
                {
                    // SLOWLOOP: does all range checks, handles all special cases, but it is slow

                    // read next char. The JIT optimization seems to be getting confused when
                    // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead
                    int ch = *pSrc;
                    pSrc++;

                    if (ch <= 0x7F)
                    {
                        if (pAllocatedBufferEnd - pTarget <= 0)
                        {
                            goto DestinationFull;
                        }

                        *pTarget = (byte)ch;
                        pTarget++;
                        continue;
                    }

                    int chd;
                    if (ch <= 0x7FF)
                    {
                        if (pAllocatedBufferEnd - pTarget <= 1)
                        {
                            goto DestinationFull;
                        }

                        // 2 byte encoding
                        chd = unchecked ((sbyte)0xC0) | (ch >> 6);
                    }
                    else
                    {
                        // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch))
                        if (!JsonHelpers.IsInRangeInclusive(ch, JsonConstants.HighSurrogateStart, JsonConstants.LowSurrogateEnd))
                        {
                            if (pAllocatedBufferEnd - pTarget <= 2)
                            {
                                goto DestinationFull;
                            }

                            // 3 byte encoding
                            chd = unchecked ((sbyte)0xE0) | (ch >> 12);
                        }
                        else
                        {
                            if (pAllocatedBufferEnd - pTarget <= 3)
                            {
                                goto DestinationFull;
                            }

                            // 4 byte encoding - high surrogate + low surrogate
                            // if (!IsHighSurrogate(ch))
                            if (ch > JsonConstants.HighSurrogateEnd)
                            {
                                // low without high -> bad
                                goto InvalidData;
                            }

                            if (pSrc >= pEnd)
                            {
                                goto NeedMoreData;
                            }

                            chd = *pSrc;

                            // if (!IsLowSurrogate(chd)) {
                            if (!JsonHelpers.IsInRangeInclusive(chd, JsonConstants.LowSurrogateStart, JsonConstants.LowSurrogateEnd))
                            {
                                // high not followed by low -> bad
                                goto InvalidData;
                            }

                            pSrc++;

                            ch = chd + (ch << 10) +
                                 (0x10000
                                  - JsonConstants.LowSurrogateStart
                                  - (JsonConstants.HighSurrogateStart << 10));

                            *pTarget = (byte)(unchecked ((sbyte)0xF0) | (ch >> 18));
                            pTarget++;

                            chd = unchecked ((sbyte)0x80) | (ch >> 12) & 0x3F;
                        }
                        *pTarget = (byte)chd;
                        pTarget++;

                        chd = unchecked ((sbyte)0x80) | (ch >> 6) & 0x3F;
                    }

                    *pTarget = (byte)chd;
                    *(pTarget + 1) = (byte)(unchecked ((sbyte)0x80) | ch & 0x3F);

                    pTarget += 2;
                }

                bytesConsumed = (int)((byte *)pSrc - chars);
                bytesWritten  = (int)(pTarget - bytes);
                return(OperationStatus.Done);

InvalidData:
                bytesConsumed = (int)((byte *)(pSrc - 1) - chars);
                bytesWritten  = (int)(pTarget - bytes);
                return(OperationStatus.InvalidData);

DestinationFull:
                bytesConsumed = (int)((byte *)(pSrc - 1) - chars);
                bytesWritten  = (int)(pTarget - bytes);
                return(OperationStatus.DestinationTooSmall);

NeedMoreData:
                bytesConsumed = (int)((byte *)(pSrc - 1) - chars);
                bytesWritten  = (int)(pTarget - bytes);
                return(OperationStatus.NeedMoreData);
            }
        }
        /// <summary>
        /// Used when writing to buffers not guaranteed to fit the unescaped result.
        /// </summary>
        private static bool TryUnescape(ReadOnlySpan <byte> source, Span <byte> destination, int idx, out int written)
        {
            Debug.Assert(idx >= 0 && idx < source.Length);
            Debug.Assert(source[idx] == JsonConstants.BackSlash);

            if (!source.Slice(0, idx).TryCopyTo(destination))
            {
                written = 0;
                goto DestinationTooShort;
            }

            written = idx;

            while (true)
            {
                Debug.Assert(source[idx] == JsonConstants.BackSlash);

                if (written == destination.Length)
                {
                    goto DestinationTooShort;
                }

                switch (source[++idx])
                {
                case JsonConstants.Quote:
                    destination[written++] = JsonConstants.Quote;
                    break;

                case (byte)'n':
                    destination[written++] = JsonConstants.LineFeed;
                    break;

                case (byte)'r':
                    destination[written++] = JsonConstants.CarriageReturn;
                    break;

                case JsonConstants.BackSlash:
                    destination[written++] = JsonConstants.BackSlash;
                    break;

                case JsonConstants.Slash:
                    destination[written++] = JsonConstants.Slash;
                    break;

                case (byte)'t':
                    destination[written++] = JsonConstants.Tab;
                    break;

                case (byte)'b':
                    destination[written++] = JsonConstants.BackSpace;
                    break;

                case (byte)'f':
                    destination[written++] = JsonConstants.FormFeed;
                    break;

                default:
                    Debug.Assert(source[idx] == 'u', "invalid escape sequences must have already been caught by Utf8JsonReader.Read()");

                    // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
                    // Otherwise, the Utf8JsonReader would have already thrown an exception.
                    Debug.Assert(source.Length >= idx + 5);

                    bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x');
                    Debug.Assert(result);
                    Debug.Assert(bytesConsumed == 4);
                    idx += 4;

                    if (JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
                    {
                        // The first hex value cannot be a low surrogate.
                        if (scalar >= JsonConstants.LowSurrogateStartValue)
                        {
                            ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(scalar);
                        }

                        Debug.Assert(JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.HighSurrogateEndValue));

                        // We must have a low surrogate following a high surrogate.
                        if (source.Length < idx + 7 || source[idx + 1] != '\\' || source[idx + 2] != 'u')
                        {
                            ThrowHelper.ThrowInvalidOperationException_ReadIncompleteUTF16();
                        }

                        // The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
                        // Otherwise, the Utf8JsonReader would have already thrown an exception.
                        result = Utf8Parser.TryParse(source.Slice(idx + 3, 4), out int lowSurrogate, out bytesConsumed, 'x');
                        Debug.Assert(result);
                        Debug.Assert(bytesConsumed == 4);
                        idx += 6;

                        // If the first hex value is a high surrogate, the next one must be a low surrogate.
                        if (!JsonHelpers.IsInRangeInclusive((uint)lowSurrogate, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
                        {
                            ThrowHelper.ThrowInvalidOperationException_ReadInvalidUTF16(lowSurrogate);
                        }

                        // To find the unicode scalar:
                        // (0x400 * (High surrogate - 0xD800)) + Low surrogate - 0xDC00 + 0x10000
                        scalar = (JsonConstants.BitShiftBy10 * (scalar - JsonConstants.HighSurrogateStartValue))
                                 + (lowSurrogate - JsonConstants.LowSurrogateStartValue)
                                 + JsonConstants.UnicodePlane01StartValue;
                    }

#if NETCOREAPP
                    var  rune    = new Rune(scalar);
                    bool success = rune.TryEncodeToUtf8(destination.Slice(written), out int bytesWritten);
#else
                    bool success = TryEncodeToUtf8Bytes((uint)scalar, destination.Slice(written), out int bytesWritten);
#endif
                    if (!success)
                    {
                        goto DestinationTooShort;
                    }

                    Debug.Assert(bytesWritten <= 4);
                    written += bytesWritten;
                    break;
                }

                if (++idx == source.Length)
                {
                    goto Success;
                }

                if (source[idx] != JsonConstants.BackSlash)
                {
                    ReadOnlySpan <byte> remaining  = source.Slice(idx);
                    int nextUnescapedSegmentLength = remaining.IndexOf(JsonConstants.BackSlash);
                    if (nextUnescapedSegmentLength < 0)
                    {
                        nextUnescapedSegmentLength = remaining.Length;
                    }

                    if ((uint)(written + nextUnescapedSegmentLength) >= (uint)destination.Length)
                    {
                        goto DestinationTooShort;
                    }

                    Debug.Assert(nextUnescapedSegmentLength > 0);
                    switch (nextUnescapedSegmentLength)
                    {
                    case 1:
                        destination[written++] = source[idx++];
                        break;

                    case 2:
                        destination[written++] = source[idx++];
                        destination[written++] = source[idx++];
                        break;

                    case 3:
                        destination[written++] = source[idx++];
                        destination[written++] = source[idx++];
                        destination[written++] = source[idx++];
                        break;

                    default:
                        remaining.Slice(0, nextUnescapedSegmentLength).CopyTo(destination.Slice(written));
                        written += nextUnescapedSegmentLength;
                        idx     += nextUnescapedSegmentLength;
                        break;
                    }

                    Debug.Assert(idx == source.Length || source[idx] == JsonConstants.BackSlash);

                    if (idx == source.Length)
                    {
                        goto Success;
                    }
                }
            }

            Success:
            return(true);

            DestinationTooShort:
            return(false);
        }
Esempio n. 18
0
        public JsonPropertyInfo GetProperty(ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame)
        {
            JsonPropertyInfo info = null;

            // If we're not trying to build the cache locally, and there is an existing cache, then use it.
            if (_propertyRefsSorted != null)
            {
                ulong key = GetKey(propertyName);

                // First try sorted lookup.
                int propertyIndex = frame.PropertyIndex;

                // This .Length is consistent no matter what json data intialized _propertyRefsSorted.
                int count = _propertyRefsSorted.Length;
                if (count != 0)
                {
                    int iForward  = Math.Min(propertyIndex, count);
                    int iBackward = iForward - 1;
                    while (iForward < count || iBackward >= 0)
                    {
                        if (iForward < count)
                        {
                            if (TryIsPropertyRefEqual(_propertyRefsSorted[iForward], propertyName, key, ref info))
                            {
                                return(info);
                            }
                            ++iForward;
                        }

                        if (iBackward >= 0)
                        {
                            if (TryIsPropertyRefEqual(_propertyRefsSorted[iBackward], propertyName, key, ref info))
                            {
                                return(info);
                            }
                            --iBackward;
                        }
                    }
                }
            }

            // Try the main list which has all of the properties in a consistent order.
            // We could get here even when hasPropertyCache==true if there is a race condition with different json
            // property ordering and _propertyRefsSorted is re-assigned while in the loop above.

            string stringPropertyName = JsonHelpers.Utf8GetString(propertyName);

            if (PropertyCache.TryGetValue(stringPropertyName, out info))
            {
                // For performance, only add to cache up to a threshold and then just use the dictionary.
                int count;
                if (_propertyRefsSorted != null)
                {
                    count = _propertyRefsSorted.Length;
                }
                else
                {
                    count = 0;
                }

                // Do a quick check for the stable (after warm-up) case.
                if (count < PropertyNameCountCacheThreshold)
                {
                    if (frame.PropertyRefCache != null)
                    {
                        count += frame.PropertyRefCache.Count;
                    }

                    // Check again to fill up to the limit.
                    if (count < PropertyNameCountCacheThreshold)
                    {
                        if (frame.PropertyRefCache == null)
                        {
                            frame.PropertyRefCache = new List <PropertyRef>();
                        }

                        ulong       key         = info.PropertyNameKey;
                        PropertyRef propertyRef = new PropertyRef(key, info);
                        frame.PropertyRefCache.Add(propertyRef);
                    }
                }
            }

            return(info);
        }
        /// <summary>
        /// Copies the UTF-8 code unit representation of this scalar to an output buffer.
        /// The buffer must be large enough to hold the required number of <see cref="byte"/>s.
        /// </summary>
        private static bool TryEncodeToUtf8Bytes(uint scalar, Span <byte> utf8Destination, out int bytesWritten)
        {
            Debug.Assert(JsonHelpers.IsValidUnicodeScalar(scalar));

            if (scalar < 0x80U)
            {
                // Single UTF-8 code unit
                if ((uint)utf8Destination.Length < 1u)
                {
                    bytesWritten = 0;
                    return(false);
                }

                utf8Destination[0] = (byte)scalar;
                bytesWritten       = 1;
            }
            else if (scalar < 0x800U)
            {
                // Two UTF-8 code units
                if ((uint)utf8Destination.Length < 2u)
                {
                    bytesWritten = 0;
                    return(false);
                }

                utf8Destination[0] = (byte)(0xC0U | (scalar >> 6));
                utf8Destination[1] = (byte)(0x80U | (scalar & 0x3FU));
                bytesWritten       = 2;
            }
            else if (scalar < 0x10000U)
            {
                // Three UTF-8 code units
                if ((uint)utf8Destination.Length < 3u)
                {
                    bytesWritten = 0;
                    return(false);
                }

                utf8Destination[0] = (byte)(0xE0U | (scalar >> 12));
                utf8Destination[1] = (byte)(0x80U | ((scalar >> 6) & 0x3FU));
                utf8Destination[2] = (byte)(0x80U | (scalar & 0x3FU));
                bytesWritten       = 3;
            }
            else
            {
                // Four UTF-8 code units
                if ((uint)utf8Destination.Length < 4u)
                {
                    bytesWritten = 0;
                    return(false);
                }

                utf8Destination[0] = (byte)(0xF0U | (scalar >> 18));
                utf8Destination[1] = (byte)(0x80U | ((scalar >> 12) & 0x3FU));
                utf8Destination[2] = (byte)(0x80U | ((scalar >> 6) & 0x3FU));
                utf8Destination[3] = (byte)(0x80U | (scalar & 0x3FU));
                bytesWritten       = 4;
            }

            return(true);
        }
Esempio n. 20
0
        public JsonClassInfo(Type type, JsonSerializerOptions options)
        {
            Type    = type;
            Options = options;

            ClassType = GetClassType(
                type,
                parentClassType: type,
                propertyInfo: null,
                out Type runtimeType,
                out Type elementType,
                out Type nullableUnderlyingType,
                out MethodInfo addMethod,
                out JsonConverter converter,
                checkForAddMethod: true,
                options);

            // Ignore properties on enumerable.
            switch (ClassType)
            {
            case ClassType.Object:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);

                PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length);

                foreach (PropertyInfo propertyInfo in properties)
                {
                    // Ignore indexers
                    if (propertyInfo.GetIndexParameters().Length > 0)
                    {
                        continue;
                    }

                    // For now we only support public getters\setters
                    if (propertyInfo.GetMethod?.IsPublic == true ||
                        propertyInfo.SetMethod?.IsPublic == true)
                    {
                        JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
                        Debug.Assert(jsonPropertyInfo != null);

                        // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
                        if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
                        {
                            JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];

                            if (other.ShouldDeserialize == false && other.ShouldSerialize == false)
                            {
                                // Overwrite the one just added since it has [JsonIgnore].
                                cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
                            }
                            else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true)
                            {
                                ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo);
                            }
                            // else ignore jsonPropertyInfo since it has [JsonIgnore].
                        }
                    }
                }

                JsonPropertyInfo[] cacheArray;
                if (DetermineExtensionDataProperty(cache))
                {
                    // Remove from cache since it is handled independently.
                    cache.Remove(DataExtensionProperty.NameAsString);

                    cacheArray = new JsonPropertyInfo[cache.Count + 1];

                    // Set the last element to the extension property.
                    cacheArray[cache.Count] = DataExtensionProperty;
                }
                else
                {
                    cacheArray = new JsonPropertyInfo[cache.Count];
                }

                // Set fields when finished to avoid concurrency issues.
                PropertyCache = cache;
                cache.Values.CopyTo(cacheArray, 0);
                PropertyCacheArray = cacheArray;
            }
            break;

            case ClassType.Enumerable:
            case ClassType.Dictionary:
            {
                // Add a single property that maps to the class type so we can have policies applied.
                ElementType     = elementType;
                AddItemToObject = addMethod;

                // A policy property is not a real property on a type; instead it leverages the existing converter
                // logic and generic support to avoid boxing. It is used with values types, elements from collections and
                // dictionaries, and collections themselves. Typically it would represent a CLR type such as System.String.
                PolicyProperty = CreateProperty(
                    declaredPropertyType: type,
                    runtimePropertyType: runtimeType,
                    propertyInfo: null,         // Not a real property so this is null.
                    parentClassType: typeof(object),
                    collectionElementType: elementType,
                    nullableUnderlyingType,
                    converter: null,
                    ClassType,
                    options);

                CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType);
            }
            break;

            case ClassType.Value:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);

                // Add a single property that maps to the class type so we can have policies applied.
                //AddPolicyPropertyForValue(type, options);
                PolicyProperty = CreateProperty(
                    declaredPropertyType: type,
                    runtimePropertyType: runtimeType,
                    propertyInfo: null,         // Not a real property so this is null.
                    parentClassType: typeof(object),
                    collectionElementType: null,
                    nullableUnderlyingType,
                    converter,
                    ClassType,
                    options);
            }
            break;

            case ClassType.Unknown:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);

                // Add a single property that maps to the class type so we can have policies applied.
                //AddPolicyPropertyForValue(type, options);
                PolicyProperty = CreateProperty(
                    declaredPropertyType: type,
                    runtimePropertyType: runtimeType,
                    propertyInfo: null,         // Not a real property so this is null.
                    parentClassType: typeof(object),
                    collectionElementType: null,
                    nullableUnderlyingType,
                    converter,
                    ClassType,
                    options);

                PropertyCache      = new Dictionary <string, JsonPropertyInfo>();
                PropertyCacheArray = Array.Empty <JsonPropertyInfo>();
            }
            break;

            default:
                Debug.Fail($"Unexpected class type: {ClassType}");
                break;
            }
        }
Esempio n. 21
0
 public static bool IsValidDateTimeOffsetParseLength(long length)
 {
     return(JsonHelpers.IsInRangeInclusive(length, JsonConstants.MinimumDateTimeParseLength, JsonConstants.MaximumEscapedDateTimeOffsetParseLength));
 }
Esempio n. 22
0
        internal JsonPropertyInfo GetProperty(JsonSerializerOptions options, ReadOnlySpan <byte> propertyName, ref ReadStackFrame frame)
        {
            // If we should compare with case-insensitive, normalize to an uppercase format since that is what is cached on the propertyInfo.
            if (options.PropertyNameCaseInsensitive)
            {
                string utf16PropertyName = JsonHelpers.Utf8GetString(propertyName);
                string upper             = utf16PropertyName.ToUpperInvariant();
                propertyName = Encoding.UTF8.GetBytes(upper);
            }

            ulong            key  = GetKey(propertyName);
            JsonPropertyInfo info = null;

            // First try sorted lookup.
            int propertyIndex = frame.PropertyIndex;

            // If we're not trying to build the cache locally, and there is an existing cache, then use it.
            bool hasPropertyCache = frame.PropertyRefCache == null && _propertyRefsSorted != null;

            if (hasPropertyCache)
            {
                // This .Length is consistent no matter what json data intialized _propertyRefsSorted.
                int count = _propertyRefsSorted.Length;
                if (count != 0)
                {
                    int iForward  = propertyIndex;
                    int iBackward = propertyIndex - 1;
                    while (iForward < count || iBackward >= 0)
                    {
                        if (iForward < count)
                        {
                            if (TryIsPropertyRefEqual(ref _propertyRefsSorted[iForward], propertyName, key, ref info))
                            {
                                return(info);
                            }
                            ++iForward;
                        }

                        if (iBackward >= 0)
                        {
                            if (TryIsPropertyRefEqual(ref _propertyRefsSorted[iBackward], propertyName, key, ref info))
                            {
                                return(info);
                            }
                            --iBackward;
                        }
                    }
                }
            }

            // Try the main list which has all of the properties in a consistent order.
            // We could get here even when hasPropertyCache==true if there is a race condition with different json
            // property ordering and _propertyRefsSorted is re-assigned while in the loop above.
            for (int i = 0; i < _propertyRefs.Count; i++)
            {
                PropertyRef propertyRef = _propertyRefs[i];
                if (TryIsPropertyRefEqual(ref propertyRef, propertyName, key, ref info))
                {
                    break;
                }
            }

            if (!hasPropertyCache)
            {
                if (propertyIndex == 0 && frame.PropertyRefCache == null)
                {
                    // Create the temporary list on first property access to prevent a partially filled List.
                    frame.PropertyRefCache = new List <PropertyRef>();
                }

                if (info != null)
                {
                    Debug.Assert(frame.PropertyRefCache != null);
                    frame.PropertyRefCache.Add(new PropertyRef(key, info));
                }
            }

            return(info);
        }
        private static void HandlePropertyName(
            JsonSerializerOptions options,
            ref Utf8JsonReader reader,
            ref ReadStack state)
        {
            if (state.Current.Drain)
            {
                return;
            }

            Debug.Assert(state.Current.ReturnValue != null || state.Current.TempDictionaryValues != null);
            Debug.Assert(state.Current.JsonClassInfo != null);

            bool isProcessingDictObject = state.Current.IsProcessingObject(ClassType.Dictionary);

            if ((isProcessingDictObject || state.Current.IsProcessingProperty(ClassType.Dictionary)) &&
                state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo)
            {
                if (isProcessingDictObject)
                {
                    state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.PolicyProperty;
                }

                state.Current.KeyName = reader.GetString();
            }
            else
            {
                Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);

                state.Current.EndProperty();

                ReadOnlySpan <byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
                if (reader._stringHasEscaping)
                {
                    int idx = propertyName.IndexOf(JsonConstants.BackSlash);
                    Debug.Assert(idx != -1);
                    propertyName = GetUnescapedString(propertyName, idx);
                }

                JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, ref state.Current);
                if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
                {
                    JsonPropertyInfo?dataExtProperty = state.Current.JsonClassInfo !.DataExtensionProperty;
                    if (dataExtProperty == null)
                    {
                        state.Current.JsonPropertyInfo = JsonPropertyInfo.s_missingProperty;
                    }
                    else
                    {
                        state.Current.JsonPropertyInfo = dataExtProperty;
                        state.Current.JsonPropertyName = propertyName.ToArray();
                        state.Current.KeyName          = JsonHelpers.Utf8GetString(propertyName);
                        state.Current.CollectionPropertyInitialized = true;

                        CreateDataExtensionProperty(dataExtProperty, ref state);
                    }
                }
                else
                {
                    // Support JsonException.Path.
                    Debug.Assert(
                        jsonPropertyInfo.JsonPropertyName == null ||
                        options.PropertyNameCaseInsensitive ||
                        propertyName.SequenceEqual(jsonPropertyInfo.JsonPropertyName));

                    state.Current.JsonPropertyInfo = jsonPropertyInfo;

                    if (jsonPropertyInfo.JsonPropertyName == null)
                    {
                        byte[] propertyNameArray = propertyName.ToArray();
                        if (options.PropertyNameCaseInsensitive)
                        {
                            // Each payload can have a different name here; remember the value on the temporary stack.
                            state.Current.JsonPropertyName = propertyNameArray;
                        }
                        else
                        {
                            // Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName
                            // so it will match the incoming payload except when case insensitivity is enabled (which is handled above).
                            state.Current.JsonPropertyInfo.JsonPropertyName = propertyNameArray;
                        }
                    }
                }

                // Increment the PropertyIndex so JsonClassInfo.GetProperty() starts with the next property.
                state.Current.PropertyIndex++;
            }
        }
Esempio n. 24
0
        public JsonClassInfo(Type type, JsonSerializerOptions options)
        {
            Type    = type;
            Options = options;

            ClassType = GetClassType(
                type,
                parentClassType: type,
                propertyInfo: null,
                out Type? runtimeType,
                out Type? elementType,
                out JsonConverter? converter,
                options);

            switch (ClassType)
            {
            case ClassType.Object:
                {
                    CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);

                    PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);

                    Dictionary <string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length);

                    foreach (PropertyInfo propertyInfo in properties)
                    {
                        // Ignore indexers
                        if (propertyInfo.GetIndexParameters().Length > 0)
                        {
                            continue;
                        }

                        // For now we only support public getters\setters
                        if (propertyInfo.GetMethod?.IsPublic == true ||
                            propertyInfo.SetMethod?.IsPublic == true)
                        {
                            JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
                            Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null);

                            // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
                            if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
                            {
                                JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];

                                if (other.ShouldDeserialize == false && other.ShouldSerialize == false)
                                {
                                    // Overwrite the one just added since it has [JsonIgnore].
                                    cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
                                }
                                else if (jsonPropertyInfo.ShouldDeserialize == true || jsonPropertyInfo.ShouldSerialize == true)
                                {
                                    ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo);
                                }
                                // else ignore jsonPropertyInfo since it has [JsonIgnore].
                            }
                        }
                    }

                    JsonPropertyInfo[] cacheArray;
                    if (DetermineExtensionDataProperty(cache))
                    {
                        // Remove from cache since it is handled independently.
                        cache.Remove(DataExtensionProperty !.NameAsString !);

                        cacheArray = new JsonPropertyInfo[cache.Count + 1];

                        // Set the last element to the extension property.
                        cacheArray[cache.Count] = DataExtensionProperty;
                    }
                    else
                    {
                        cacheArray = new JsonPropertyInfo[cache.Count];
                    }

                    // Set fields when finished to avoid concurrency issues.
                    PropertyCache = cache;
                    cache.Values.CopyTo(cacheArray, 0);
                    PropertyCacheArray = cacheArray;

                    // Create the policy property.
                    PolicyProperty = CreatePolicyProperty(type, runtimeType, converter !, ClassType, options);
                }
                break;

            case ClassType.Enumerable:
            case ClassType.Dictionary:
                {
                    ElementType    = elementType;
                    PolicyProperty = CreatePolicyProperty(type, runtimeType, converter !, ClassType, options);
                    CreateObject   = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType !);
                }
                break;

            case ClassType.Value:
            case ClassType.NewValue:
            {
                CreateObject   = options.MemberAccessorStrategy.CreateConstructor(type);
                PolicyProperty = CreatePolicyProperty(type, runtimeType, converter !, ClassType, options);
            }
            break;

            case ClassType.Invalid:
            {
                ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
            }
            break;

            default:
                Debug.Fail($"Unexpected class type: {ClassType}");
                break;
            }
        }
Esempio n. 25
0
        public JsonClassInfo(Type type, JsonSerializerOptions options)
        {
            Type    = type;
            Options = options;

            JsonConverter converter = GetConverter(
                Type,
                parentClassType: null, // A ClassInfo never has a "parent" class.
                propertyInfo: null,    // A ClassInfo never has a "parent" property.
                out Type runtimeType,
                Options);

            ClassType = converter.ClassType;
            PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(Type, runtimeType, converter, Options);

            switch (ClassType)
            {
            case ClassType.Object:
            {
                CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
                Dictionary <string, JsonPropertyInfo> cache = new Dictionary <string, JsonPropertyInfo>(
                    Options.PropertyNameCaseInsensitive
                                ? StringComparer.OrdinalIgnoreCase
                                : StringComparer.Ordinal);

                Dictionary <string, PropertyInfo>?ignoredProperties = null;

                // We start from the most derived type.
                for (Type?currentType = type; currentType != null; currentType = currentType.BaseType)
                {
                    foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
                    {
                        // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d.
                        if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyInfo, ignoredProperties))
                        {
                            continue;
                        }

                        // For now we only support public properties (i.e. setter and/or getter is public).
                        if (propertyInfo.GetMethod?.IsPublic == true ||
                            propertyInfo.SetMethod?.IsPublic == true)
                        {
                            JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options);
                            Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null);

                            string propertyName = propertyInfo.Name;

                            // The JsonPropertyNameAttribute or naming policy resulted in a collision.
                            if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
                            {
                                JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];

                                if (other.IsIgnored)
                                {
                                    // Overwrite previously cached property since it has [JsonIgnore].
                                    cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
                                }
                                else if (
                                    // Does the current property have `JsonIgnoreAttribute`?
                                    !jsonPropertyInfo.IsIgnored &&
                                    // Is the current property hidden by the previously cached property
                                    // (with `new` keyword, or by overriding)?
                                    other.PropertyInfo !.Name != propertyName &&
                                    // Was a property with the same CLR name ignored? That property hid the current property,
                                    // thus, if it was ignored, the current property should be ignored too.
                                    ignoredProperties?.ContainsKey(propertyName) != true
                                    )
                                {
                                    // Throw if we have two public properties with the same JSON property name,
                                    // neither overrides or hides the other, and neither have been ignored.
                                    ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo);
                                }
                                // Ignore the current property.
                            }

                            if (jsonPropertyInfo.IsIgnored)
                            {
                                (ignoredProperties ??= new Dictionary <string, PropertyInfo>())[propertyName] = propertyInfo;
Esempio n. 26
0
        private static void ReadCore(
            JsonSerializerOptions options,
            ref Utf8JsonReader reader,
            ref ReadStack readStack)
        {
            try
            {
                JsonReaderState initialState = default;

                while (true)
                {
                    if (readStack.ReadAhead)
                    {
                        // When we're reading ahead we always have to save the state
                        // as we don't know if the next token is an opening object or
                        // array brace.
                        initialState = reader.CurrentState;
                    }

                    if (!reader.Read())
                    {
                        // Need more data
                        break;
                    }

                    JsonTokenType tokenType = reader.TokenType;

                    if (JsonHelpers.IsInRangeInclusive(tokenType, JsonTokenType.String, JsonTokenType.False))
                    {
                        Debug.Assert(tokenType == JsonTokenType.String || tokenType == JsonTokenType.Number || tokenType == JsonTokenType.True || tokenType == JsonTokenType.False);

                        HandleValue(tokenType, options, ref reader, ref readStack);
                    }
                    else if (tokenType == JsonTokenType.PropertyName)
                    {
                        HandlePropertyName(options, ref reader, ref readStack);
                    }
                    else if (tokenType == JsonTokenType.StartObject)
                    {
                        if (readStack.Current.SkipProperty)
                        {
                            readStack.Push();
                            readStack.Current.Drain = true;
                        }
                        else if (readStack.Current.IsProcessingValue)
                        {
                            if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState))
                            {
                                // Need more data
                                break;
                            }
                        }
                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary)
                        {
                            HandleStartDictionary(options, ref reader, ref readStack);
                        }
                        else
                        {
                            HandleStartObject(options, ref reader, ref readStack);
                        }
                    }
                    else if (tokenType == JsonTokenType.EndObject)
                    {
                        if (readStack.Current.Drain)
                        {
                            readStack.Pop();
                        }
                        else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary)
                        {
                            HandleEndDictionary(options, ref reader, ref readStack);
                        }
                        else
                        {
                            HandleEndObject(options, ref reader, ref readStack);
                        }
                    }
                    else if (tokenType == JsonTokenType.StartArray)
                    {
                        if (!readStack.Current.IsProcessingValue)
                        {
                            HandleStartArray(options, ref reader, ref readStack);
                        }
                        else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState))
                        {
                            // Need more data
                            break;
                        }
                    }
                    else if (tokenType == JsonTokenType.EndArray)
                    {
                        HandleEndArray(options, ref reader, ref readStack);
                    }
                    else if (tokenType == JsonTokenType.Null)
                    {
                        HandleNull(ref reader, ref readStack, options);
                    }
                }
            }
            catch (JsonReaderException e)
            {
                // Re-throw with Path information.
                ThrowHelper.ReThrowWithPath(e, readStack.JsonPath);
            }

            readStack.BytesConsumed += reader.BytesConsumed;
            return;
        }
        internal static void ValidateNumber(ReadOnlySpan <byte> utf8FormattedNumber)
        {
            // This is a simplified version of the number reader from Utf8JsonReader.TryGetNumber,
            // because it doesn't need to deal with "NeedsMoreData", or remembering the format.
            //
            // The Debug.Asserts in this method should change to validated ArgumentExceptions if/when
            // writing a formatted number becomes public API.
            Debug.Assert(!utf8FormattedNumber.IsEmpty);

            int i = 0;

            if (utf8FormattedNumber[i] == '-')
            {
                i++;

                if (utf8FormattedNumber.Length <= i)
                {
                    throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
                }
            }

            if (utf8FormattedNumber[i] == '0')
            {
                i++;
            }
            else
            {
                while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
                {
                    i++;
                }
            }

            if (i == utf8FormattedNumber.Length)
            {
                return;
            }

            // The non digit character inside the number
            byte val = utf8FormattedNumber[i];

            if (val == '.')
            {
                i++;

                if (utf8FormattedNumber.Length <= i)
                {
                    throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
                }

                while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
                {
                    i++;
                }

                if (i == utf8FormattedNumber.Length)
                {
                    return;
                }

                Debug.Assert(i < utf8FormattedNumber.Length);
                val = utf8FormattedNumber[i];
            }

            if (val == 'e' || val == 'E')
            {
                i++;

                if (utf8FormattedNumber.Length <= i)
                {
                    throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
                }

                val = utf8FormattedNumber[i];

                if (val == '+' || val == '-')
                {
                    i++;
                }
            }
            else
            {
                throw new ArgumentException(
                          SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(val)),
                          nameof(utf8FormattedNumber));
            }

            if (utf8FormattedNumber.Length <= i)
            {
                throw new ArgumentException(SR.RequiredDigitNotFoundEndOfData, nameof(utf8FormattedNumber));
            }

            while (i < utf8FormattedNumber.Length && JsonHelpers.IsDigit(utf8FormattedNumber[i]))
            {
                i++;
            }

            if (i != utf8FormattedNumber.Length)
            {
                throw new ArgumentException(
                          SR.Format(SR.ExpectedEndOfDigitNotFound, ThrowHelper.GetPrintableString(utf8FormattedNumber[i])),
                          nameof(utf8FormattedNumber));
            }
        }