< Summary

Information
Line coverage
20%
Covered lines: 44
Uncovered lines: 175
Coverable lines: 219
Total lines: 379
Line coverage: 20%
Branch coverage
24%
Covered branches: 24
Total branches: 100
Branch coverage: 24%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\Serialization\Converters\Collection\JsonDictionaryConverter.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6using System.Text.Json.Serialization.Metadata;
 7
 8namespace System.Text.Json.Serialization
 9{
 10    /// <summary>
 11    /// Base class for dictionary converters such as IDictionary, Hashtable, Dictionary{,} IDictionary{,} and SortedList
 12    /// </summary>
 13    internal abstract class JsonDictionaryConverter<TDictionary> : JsonResumableConverter<TDictionary>
 14    {
 15        internal override bool SupportsCreateObjectDelegate => true;
 16        private protected sealed override ConverterStrategy GetDefaultConverterStrategy() => ConverterStrategy.Dictionar
 17
 18        protected internal abstract bool OnWriteResume(Utf8JsonWriter writer, TDictionary dictionary, JsonSerializerOpti
 19    }
 20
 21    /// <summary>
 22    /// Base class for dictionary converters such as IDictionary, Hashtable, Dictionary{,} IDictionary{,} and SortedList
 23    /// </summary>
 24    internal abstract class JsonDictionaryConverter<TDictionary, TKey, TValue> : JsonDictionaryConverter<TDictionary>
 25        where TKey : notnull
 26    {
 27        /// <summary>
 28        /// When overridden, adds the value to the collection.
 29        /// </summary>
 30        protected abstract void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state);
 31
 32        /// <summary>
 33        /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection
 34        /// This is used with immutable collections.
 35        /// </summary>
 036        protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
 37
 38        /// <summary>
 39        /// When overridden, create the collection. It may be a temporary collection or the final collection.
 40        /// </summary>
 41        protected virtual void CreateCollection(ref Utf8JsonReader reader, scoped ref ReadStack state)
 6042        {
 6043            if (state.ParentProperty?.TryGetPrePopulatedValue(ref state) == true)
 044            {
 045                return;
 46            }
 47
 6048            JsonTypeInfo typeInfo = state.Current.JsonTypeInfo;
 49
 6050            if (typeInfo.CreateObject is null)
 051            {
 052                ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(typeInfo, ref reader, ref state);
 53            }
 54
 6055            state.Current.ReturnValue = typeInfo.CreateObject();
 6056            Debug.Assert(state.Current.ReturnValue is TDictionary);
 6057        }
 58
 202459        internal override Type ElementType => typeof(TValue);
 60
 36461        internal override Type KeyType => typeof(TKey);
 62
 63
 64        protected JsonConverter<TKey>? _keyConverter;
 65        protected JsonConverter<TValue>? _valueConverter;
 66
 67        protected static JsonConverter<T> GetConverter<T>(JsonTypeInfo typeInfo)
 1668        {
 1669            return ((JsonTypeInfo<T>)typeInfo).EffectiveConverter;
 1670        }
 71
 72        internal sealed override bool OnTryRead(
 73            ref Utf8JsonReader reader,
 74            Type typeToConvert,
 75            JsonSerializerOptions options,
 76            scoped ref ReadStack state,
 77            [MaybeNullWhen(false)] out TDictionary value)
 65078        {
 65079            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 65080            JsonTypeInfo keyTypeInfo = jsonTypeInfo.KeyTypeInfo!;
 65081            JsonTypeInfo elementTypeInfo = jsonTypeInfo.ElementTypeInfo!;
 82
 65083            if (!state.SupportContinuation && !state.Current.CanContainMetadata)
 084            {
 85                // Fast path that avoids maintaining state variables and dealing with preserved references.
 86
 087                if (reader.TokenType != JsonTokenType.StartObject)
 088                {
 089                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 90                }
 91
 092                CreateCollection(ref reader, ref state);
 93
 094                jsonTypeInfo.OnDeserializing?.Invoke(state.Current.ReturnValue!);
 95
 096                _keyConverter ??= GetConverter<TKey>(keyTypeInfo);
 097                _valueConverter ??= GetConverter<TValue>(elementTypeInfo);
 98
 099                if (_valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
 0100                {
 101                    // Process all elements.
 0102                    while (true)
 0103                    {
 104                        // Read the key name.
 0105                        reader.ReadWithVerify();
 106
 0107                        if (reader.TokenType == JsonTokenType.EndObject)
 0108                        {
 0109                            break;
 110                        }
 111
 112                        // Read method would have thrown if otherwise.
 0113                        Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 114
 0115                        state.Current.JsonPropertyInfo = keyTypeInfo.PropertyInfoForTypeInfo;
 0116                        TKey key = ReadDictionaryKey(_keyConverter, ref reader, ref state, options);
 117
 118                        // Read the value and add.
 0119                        reader.ReadWithVerify();
 0120                        state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 0121                        TValue? element = _valueConverter.Read(ref reader, ElementType, options);
 0122                        Add(key, element!, options, ref state);
 0123                    }
 0124                }
 125                else
 0126                {
 127                    // Process all elements.
 0128                    while (true)
 0129                    {
 130                        // Read the key name.
 0131                        reader.ReadWithVerify();
 132
 0133                        if (reader.TokenType == JsonTokenType.EndObject)
 0134                        {
 0135                            break;
 136                        }
 137
 138                        // Read method would have thrown if otherwise.
 0139                        Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 0140                        state.Current.JsonPropertyInfo = keyTypeInfo.PropertyInfoForTypeInfo;
 0141                        TKey key = ReadDictionaryKey(_keyConverter, ref reader, ref state, options);
 142
 0143                        reader.ReadWithVerify();
 144
 145                        // Get the value from the converter and add it.
 0146                        state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 0147                        _valueConverter.TryRead(ref reader, ElementType, options, ref state, out TValue? element, out _)
 0148                        Add(key, element!, options, ref state);
 0149                    }
 0150                }
 0151            }
 152            else
 650153            {
 154                // Slower path that supports continuation and reading metadata.
 650155                if (state.Current.ObjectState == StackFrameObjectState.None)
 650156                {
 650157                    if (reader.TokenType != JsonTokenType.StartObject)
 590158                    {
 590159                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 160                    }
 161
 60162                    state.Current.ObjectState = StackFrameObjectState.StartToken;
 60163                }
 164
 165                // Handle the metadata properties.
 60166                if (state.Current.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
 0167                {
 0168                    if (!JsonSerializer.TryReadMetadata(this, jsonTypeInfo, ref reader, ref state))
 0169                    {
 0170                        value = default;
 0171                        return false;
 172                    }
 173
 0174                    if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
 0175                    {
 0176                        value = JsonSerializer.ResolveReferenceId<TDictionary>(ref state);
 0177                        return true;
 178                    }
 179
 0180                    state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
 0181                }
 182
 183                // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
 60184                if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 &&
 60185                    state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStart
 60186                    ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
 0187                {
 0188                    Debug.Assert(!IsValueType);
 0189                    bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.Type!, option
 0190                    value = (TDictionary)objectResult!;
 0191                    state.ExitPolymorphicConverter(success);
 0192                    return success;
 193                }
 194
 195                // Create the dictionary.
 60196                if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
 60197                {
 60198                    if (state.Current.CanContainMetadata)
 0199                    {
 0200                        JsonSerializer.ValidateMetadataForObjectConverter(ref state);
 0201                    }
 202
 60203                    CreateCollection(ref reader, ref state);
 204
 60205                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
 0206                    {
 0207                        Debug.Assert(state.ReferenceId != null);
 0208                        Debug.Assert(options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve);
 0209                        Debug.Assert(state.Current.ReturnValue is TDictionary);
 0210                        state.ReferenceResolver.AddReference(state.ReferenceId, state.Current.ReturnValue);
 0211                        state.ReferenceId = null;
 0212                    }
 213
 60214                    jsonTypeInfo.OnDeserializing?.Invoke(state.Current.ReturnValue!);
 215
 60216                    state.Current.ObjectState = StackFrameObjectState.CreatedObject;
 60217                }
 218
 219                // Process all elements.
 60220                _keyConverter ??= GetConverter<TKey>(keyTypeInfo);
 60221                _valueConverter ??= GetConverter<TValue>(elementTypeInfo);
 60222                while (true)
 60223                {
 60224                    if (state.Current.PropertyState == StackFramePropertyState.None)
 60225                    {
 226                        // Read the key name.
 60227                        if (!reader.Read())
 0228                        {
 0229                            value = default;
 0230                            return false;
 231                        }
 232
 0233                        state.Current.PropertyState = StackFramePropertyState.ReadName;
 0234                    }
 235
 236                    // Determine the property.
 237                    TKey key;
 0238                    if (state.Current.PropertyState < StackFramePropertyState.Name)
 0239                    {
 0240                        if (reader.TokenType == JsonTokenType.EndObject)
 0241                        {
 0242                            break;
 243                        }
 244
 245                        // Read method would have thrown if otherwise.
 0246                        Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 247
 0248                        state.Current.PropertyState = StackFramePropertyState.Name;
 249
 0250                        if (state.Current.CanContainMetadata)
 0251                        {
 0252                            ReadOnlySpan<byte> propertyName = reader.GetUnescapedSpan();
 0253                            if (JsonSerializer.IsMetadataPropertyName(propertyName, state.Current.BaseJsonTypeInfo.Polym
 0254                            {
 0255                                if (options.AllowOutOfOrderMetadataProperties)
 0256                                {
 0257                                    reader.SkipWithVerify();
 0258                                    state.Current.EndElement();
 0259                                    continue;
 260                                }
 261                                else
 0262                                {
 0263                                    ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
 264                                }
 265                            }
 0266                        }
 267
 0268                        state.Current.JsonPropertyInfo = keyTypeInfo.PropertyInfoForTypeInfo;
 0269                        key = ReadDictionaryKey(_keyConverter, ref reader, ref state, options);
 0270                    }
 271                    else
 0272                    {
 273                        // DictionaryKey is assigned before all return false cases, null value is unreachable
 0274                        key = (TKey)state.Current.DictionaryKey!;
 0275                    }
 276
 0277                    if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
 0278                    {
 0279                        if (!reader.TryAdvanceWithOptionalReadAhead(_valueConverter.RequiresReadAhead))
 0280                        {
 0281                            state.Current.DictionaryKey = key;
 0282                            value = default;
 0283                            return false;
 284                        }
 285
 0286                        state.Current.PropertyState = StackFramePropertyState.ReadValue;
 0287                    }
 288
 0289                    if (state.Current.PropertyState < StackFramePropertyState.TryRead)
 0290                    {
 291                        // Get the value from the converter and add it.
 0292                        state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 0293                        bool success = _valueConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValu
 0294                        if (!success)
 0295                        {
 0296                            state.Current.DictionaryKey = key;
 0297                            value = default;
 0298                            return false;
 299                        }
 300
 0301                        Add(key, element!, options, ref state);
 0302                        state.Current.EndElement();
 0303                    }
 0304                }
 0305            }
 306
 0307            ConvertCollection(ref state, options);
 0308            object result = state.Current.ReturnValue!;
 0309            jsonTypeInfo.OnDeserialized?.Invoke(result);
 0310            value = (TDictionary)result;
 311
 0312            return true;
 313
 314            static TKey ReadDictionaryKey(JsonConverter<TKey> keyConverter, ref Utf8JsonReader reader, scoped ref ReadSt
 0315            {
 316                TKey key;
 0317                string unescapedPropertyNameAsString = reader.GetString()!;
 0318                state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; // Copy key name for JSON Path s
 319
 320                // Special case string to avoid calling GetString twice and save one allocation.
 0321                if (keyConverter.IsInternalConverter && keyConverter.Type == typeof(string))
 0322                {
 0323                    key = (TKey)(object)unescapedPropertyNameAsString;
 0324                }
 325                else
 0326                {
 0327                    key = keyConverter.ReadAsPropertyNameCore(ref reader, keyConverter.Type, options);
 0328                }
 329
 0330                return key;
 0331            }
 0332        }
 333
 334        internal sealed override bool OnTryWrite(
 335            Utf8JsonWriter writer,
 336            TDictionary dictionary,
 337            JsonSerializerOptions options,
 338            ref WriteStack state)
 0339        {
 0340            if (dictionary == null)
 0341            {
 0342                writer.WriteNullValue();
 0343                return true;
 344            }
 345
 0346            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 347
 0348            if (!state.Current.ProcessedStartToken)
 0349            {
 0350                state.Current.ProcessedStartToken = true;
 351
 0352                jsonTypeInfo.OnSerializing?.Invoke(dictionary);
 353
 0354                writer.WriteStartObject();
 355
 0356                if (state.CurrentContainsMetadata && CanHaveMetadata)
 0357                {
 0358                    JsonSerializer.WriteMetadataForObject(this, ref state, writer);
 0359                }
 360
 0361                state.Current.JsonPropertyInfo = jsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
 0362            }
 363
 0364            bool success = OnWriteResume(writer, dictionary, options, ref state);
 0365            if (success)
 0366            {
 0367                if (!state.Current.ProcessedEndToken)
 0368                {
 0369                    state.Current.ProcessedEndToken = true;
 0370                    writer.WriteEndObject();
 0371                }
 372
 0373                jsonTypeInfo.OnSerialized?.Invoke(dictionary);
 0374            }
 375
 0376            return success;
 0377        }
 378    }
 379}