< Summary

Line coverage
66%
Covered lines: 2
Uncovered lines: 1
Coverable lines: 3
Total lines: 381
Line coverage: 66.6%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
GetDefaultConverterStrategy()100%11100%
GetSupportedJsonValueTypes(...)100%110%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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    {
 54215        internal override bool SupportsCreateObjectDelegate => true;
 27116        private protected sealed override ConverterStrategy GetDefaultConverterStrategy() => ConverterStrategy.Dictionar
 017        internal sealed override JsonValueType GetSupportedJsonValueTypes(JsonNumberHandling _) => JsonValueType.Object;
 18
 19        protected internal abstract bool OnWriteResume(Utf8JsonWriter writer, TDictionary dictionary, JsonSerializerOpti
 20    }
 21
 22    /// <summary>
 23    /// Base class for dictionary converters such as IDictionary, Hashtable, Dictionary{,} IDictionary{,} and SortedList
 24    /// </summary>
 25    internal abstract class JsonDictionaryConverter<TDictionary, TKey, TValue> : JsonDictionaryConverter<TDictionary>
 26        where TKey : notnull
 27    {
 28        /// <summary>
 29        /// When overridden, adds the value to the collection.
 30        /// </summary>
 31        protected abstract void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state);
 32
 33        /// <summary>
 34        /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection
 35        /// This is used with immutable collections.
 36        /// </summary>
 37        protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
 38
 39        /// <summary>
 40        /// When overridden, create the collection. It may be a temporary collection or the final collection.
 41        /// </summary>
 42        protected virtual void CreateCollection(ref Utf8JsonReader reader, scoped ref ReadStack state)
 43        {
 44            if (state.ParentProperty?.TryGetPrePopulatedValue(ref state) == true)
 45            {
 46                return;
 47            }
 48
 49            JsonTypeInfo typeInfo = state.Current.JsonTypeInfo;
 50
 51            if (typeInfo.CreateObject is null)
 52            {
 53                ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(typeInfo, ref reader, ref state);
 54            }
 55
 56            state.Current.ReturnValue = typeInfo.CreateObject();
 57            Debug.Assert(state.Current.ReturnValue is TDictionary);
 58        }
 59
 60        internal override Type ElementType => typeof(TValue);
 61
 62        internal override Type KeyType => typeof(TKey);
 63
 64
 65        protected JsonConverter<TKey>? _keyConverter;
 66        protected JsonConverter<TValue>? _valueConverter;
 67
 68        protected static JsonConverter<T> GetConverter<T>(JsonTypeInfo typeInfo)
 69        {
 70            return ((JsonTypeInfo<T>)typeInfo).EffectiveConverter;
 71        }
 72
 73        internal sealed override bool OnTryRead(
 74            ref Utf8JsonReader reader,
 75            Type typeToConvert,
 76            JsonSerializerOptions options,
 77            scoped ref ReadStack state,
 78            [MaybeNullWhen(false)] out TDictionary value)
 79        {
 80            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 81            JsonTypeInfo keyTypeInfo = jsonTypeInfo.KeyTypeInfo!;
 82            JsonTypeInfo elementTypeInfo = jsonTypeInfo.ElementTypeInfo!;
 83
 84            if (!state.SupportContinuation && !state.Current.CanContainMetadata)
 85            {
 86                // Fast path that avoids maintaining state variables and dealing with preserved references.
 87
 88                if (reader.TokenType != JsonTokenType.StartObject)
 89                {
 90                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 91                }
 92
 93                CreateCollection(ref reader, ref state);
 94
 95                jsonTypeInfo.OnDeserializing?.Invoke(state.Current.ReturnValue!);
 96
 97                _keyConverter ??= GetConverter<TKey>(keyTypeInfo);
 98                _valueConverter ??= GetConverter<TValue>(elementTypeInfo);
 99
 100                if (_valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling is null)
 101                {
 102                    // Process all elements.
 103                    while (true)
 104                    {
 105                        // Read the key name.
 106                        reader.ReadWithVerify();
 107
 108                        if (reader.TokenType == JsonTokenType.EndObject)
 109                        {
 110                            break;
 111                        }
 112
 113                        // Read method would have thrown if otherwise.
 114                        Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 115
 116                        state.Current.JsonPropertyInfo = keyTypeInfo.PropertyInfoForTypeInfo;
 117                        TKey key = ReadDictionaryKey(_keyConverter, ref reader, ref state, options);
 118
 119                        // Read the value and add.
 120                        reader.ReadWithVerify();
 121                        state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 122                        TValue? element = _valueConverter.Read(ref reader, ElementType, options);
 123                        Add(key, element!, options, ref state);
 124                    }
 125                }
 126                else
 127                {
 128                    // Process all elements.
 129                    while (true)
 130                    {
 131                        // Read the key name.
 132                        reader.ReadWithVerify();
 133
 134                        if (reader.TokenType == JsonTokenType.EndObject)
 135                        {
 136                            break;
 137                        }
 138
 139                        // Read method would have thrown if otherwise.
 140                        Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 141                        state.Current.JsonPropertyInfo = keyTypeInfo.PropertyInfoForTypeInfo;
 142                        TKey key = ReadDictionaryKey(_keyConverter, ref reader, ref state, options);
 143
 144                        reader.ReadWithVerify();
 145
 146                        // Get the value from the converter and add it.
 147                        state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 148                        _valueConverter.TryRead(ref reader, ElementType, options, ref state, out TValue? element, out _)
 149                        Add(key, element!, options, ref state);
 150                    }
 151                }
 152            }
 153            else
 154            {
 155                // Slower path that supports continuation and reading metadata.
 156                if (state.Current.ObjectState == StackFrameObjectState.None)
 157                {
 158                    if (reader.TokenType != JsonTokenType.StartObject)
 159                    {
 160                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 161                    }
 162
 163                    state.Current.ObjectState = StackFrameObjectState.StartToken;
 164                }
 165
 166                // Handle the metadata properties.
 167                if (state.Current.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
 168                {
 169                    if (!JsonSerializer.TryReadMetadata(this, jsonTypeInfo, ref reader, ref state))
 170                    {
 171                        value = default;
 172                        return false;
 173                    }
 174
 175                    if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
 176                    {
 177                        value = JsonSerializer.ResolveReferenceId<TDictionary>(ref state);
 178                        return true;
 179                    }
 180
 181                    state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
 182                }
 183
 184                // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
 185                if (((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 || state.PolymorphicResolved
 186                    state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStart
 187                    ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
 188                {
 189                    Debug.Assert(!IsValueType);
 190                    bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.Type!, option
 191                    value = (TDictionary)objectResult!;
 192                    state.ExitPolymorphicConverter(success);
 193                    return success;
 194                }
 195
 196                // Create the dictionary.
 197                if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
 198                {
 199                    if (state.Current.CanContainMetadata)
 200                    {
 201                        JsonSerializer.ValidateMetadataForObjectConverter(ref state);
 202                    }
 203
 204                    CreateCollection(ref reader, ref state);
 205
 206                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
 207                    {
 208                        Debug.Assert(state.ReferenceId is not null);
 209                        Debug.Assert(options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve);
 210                        Debug.Assert(state.Current.ReturnValue is TDictionary);
 211                        state.ReferenceResolver.AddReference(state.ReferenceId, state.Current.ReturnValue);
 212                        state.ReferenceId = null;
 213                    }
 214
 215                    jsonTypeInfo.OnDeserializing?.Invoke(state.Current.ReturnValue!);
 216
 217                    state.Current.ObjectState = StackFrameObjectState.CreatedObject;
 218                }
 219
 220                // Process all elements.
 221                _keyConverter ??= GetConverter<TKey>(keyTypeInfo);
 222                _valueConverter ??= GetConverter<TValue>(elementTypeInfo);
 223                while (true)
 224                {
 225                    if (state.Current.PropertyState == StackFramePropertyState.None)
 226                    {
 227                        // Read the key name.
 228                        if (!reader.Read())
 229                        {
 230                            value = default;
 231                            return false;
 232                        }
 233
 234                        state.Current.PropertyState = StackFramePropertyState.ReadName;
 235                    }
 236
 237                    // Determine the property.
 238                    TKey key;
 239                    if (state.Current.PropertyState < StackFramePropertyState.Name)
 240                    {
 241                        if (reader.TokenType == JsonTokenType.EndObject)
 242                        {
 243                            break;
 244                        }
 245
 246                        // Read method would have thrown if otherwise.
 247                        Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 248
 249                        state.Current.PropertyState = StackFramePropertyState.Name;
 250
 251                        if (state.Current.CanContainMetadata)
 252                        {
 253                            ReadOnlySpan<byte> propertyName = reader.GetUnescapedSpan();
 254                            if (JsonSerializer.IsMetadataPropertyName(propertyName, state.Current.BaseJsonTypeInfo.Polym
 255                            {
 256                                if (options.AllowOutOfOrderMetadataProperties)
 257                                {
 258                                    reader.SkipWithVerify();
 259                                    state.Current.EndElement();
 260                                    continue;
 261                                }
 262                                else
 263                                {
 264                                    ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state);
 265                                }
 266                            }
 267                        }
 268
 269                        state.Current.JsonPropertyInfo = keyTypeInfo.PropertyInfoForTypeInfo;
 270                        key = ReadDictionaryKey(_keyConverter, ref reader, ref state, options);
 271                    }
 272                    else
 273                    {
 274                        // DictionaryKey is assigned before all return false cases, null value is unreachable
 275                        key = (TKey)state.Current.DictionaryKey!;
 276                    }
 277
 278                    if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
 279                    {
 280                        if (!reader.TryAdvanceWithOptionalReadAhead(_valueConverter.RequiresReadAhead))
 281                        {
 282                            state.Current.DictionaryKey = key;
 283                            value = default;
 284                            return false;
 285                        }
 286
 287                        state.Current.PropertyState = StackFramePropertyState.ReadValue;
 288                    }
 289
 290                    if (state.Current.PropertyState < StackFramePropertyState.TryRead)
 291                    {
 292                        // Get the value from the converter and add it.
 293                        state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 294                        bool success = _valueConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValu
 295                        if (!success)
 296                        {
 297                            state.Current.DictionaryKey = key;
 298                            value = default;
 299                            return false;
 300                        }
 301
 302                        Add(key, element!, options, ref state);
 303                        state.Current.EndElement();
 304                    }
 305                }
 306            }
 307
 308            ConvertCollection(ref state, options);
 309            object result = state.Current.ReturnValue!;
 310            jsonTypeInfo.OnDeserialized?.Invoke(result);
 311            value = (TDictionary)result;
 312
 313            return true;
 314
 315            static TKey ReadDictionaryKey(JsonConverter<TKey> keyConverter, ref Utf8JsonReader reader, scoped ref ReadSt
 316            {
 317                TKey key;
 318                string unescapedPropertyNameAsString = reader.GetString()!;
 319                state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; // Copy key name for JSON Path s
 320
 321                // Special case string to avoid calling GetString twice and save one allocation.
 322                if (keyConverter.IsInternalConverter && keyConverter.Type == typeof(string))
 323                {
 324                    key = (TKey)(object)unescapedPropertyNameAsString;
 325                }
 326                else
 327                {
 328                    key = keyConverter.ReadAsPropertyNameCore(ref reader, keyConverter.Type, options);
 329                }
 330
 331                return key;
 332            }
 333        }
 334
 335        internal sealed override bool OnTryWrite(
 336            Utf8JsonWriter writer,
 337            TDictionary dictionary,
 338            JsonSerializerOptions options,
 339            ref WriteStack state)
 340        {
 341            if (dictionary is null)
 342            {
 343                writer.WriteNullValue();
 344                return true;
 345            }
 346
 347            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 348
 349            if (!state.Current.ProcessedStartToken)
 350            {
 351                state.Current.ProcessedStartToken = true;
 352
 353                jsonTypeInfo.OnSerializing?.Invoke(dictionary);
 354
 355                writer.WriteStartObject();
 356
 357                if (state.CurrentContainsMetadata && CanHaveMetadata)
 358                {
 359                    JsonSerializer.WriteMetadataForObject(this, ref state, writer);
 360                }
 361
 362                state.Current.JsonPropertyInfo = jsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
 363            }
 364
 365            bool success = OnWriteResume(writer, dictionary, options, ref state);
 366            if (success)
 367            {
 368                if (!state.Current.ProcessedEndToken)
 369                {
 370                    state.Current.ProcessedEndToken = true;
 371                    writer.WriteEndObject();
 372                }
 373
 374                jsonTypeInfo.OnSerialized?.Invoke(dictionary);
 375            }
 376
 377            return success;
 378        }
 379    }
 380}
 381