< Summary

Line coverage
19%
Covered lines: 42
Uncovered lines: 179
Coverable lines: 221
Total lines: 350
Line coverage: 19%
Branch coverage
17%
Covered branches: 18
Total branches: 104
Branch coverage: 17.3%
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%
CreateCollection(...)0%660%
ConvertCollection(...)100%110%
GetElementConverter(...)100%11100%
GetElementConverter(...)100%110%
OnTryRead(...)22.5%808022.22%
OnTryWrite(...)0%18180%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.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.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Text.Json.Serialization.Metadata;
 8
 9namespace System.Text.Json.Serialization
 10{
 11    /// <summary>
 12    /// Base class for all collections. Collections are assumed to implement <see cref="IEnumerable{T}"/>
 13    /// or a variant thereof e.g. <see cref="IAsyncEnumerable{T}"/>.
 14    /// </summary>
 15    internal abstract class JsonCollectionConverter<TCollection, TElement> : JsonResumableConverter<TCollection>
 16    {
 39617        internal override bool SupportsCreateObjectDelegate => true;
 19818        private protected sealed override ConverterStrategy GetDefaultConverterStrategy() => ConverterStrategy.Enumerabl
 019        internal sealed override JsonValueType GetSupportedJsonValueTypes(JsonNumberHandling _) => JsonValueType.Array;
 131620        internal override Type ElementType => typeof(TElement);
 21
 22        protected abstract void Add(in TElement value, ref ReadStack state);
 23
 24        /// <summary>
 25        /// When overridden, create the collection. It may be a temporary collection or the final collection.
 26        /// </summary>
 27        protected virtual void CreateCollection(ref Utf8JsonReader reader, scoped ref ReadStack state, JsonSerializerOpt
 028        {
 029            if (state.ParentProperty?.TryGetPrePopulatedValue(ref state) == true)
 030            {
 031                return;
 32            }
 33
 034            JsonTypeInfo typeInfo = state.Current.JsonTypeInfo;
 35
 036            if (typeInfo.CreateObject is null)
 037            {
 038                ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(typeInfo, ref reader, ref state);
 39            }
 40
 041            state.Current.ReturnValue = typeInfo.CreateObject();
 042            Debug.Assert(state.Current.ReturnValue is TCollection);
 043        }
 44
 45        /// <summary>
 46        /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection
 47        /// The <see cref="JsonConverter.IsConvertibleCollection"/> property must also be set to <see langword="true"/>.
 48        /// </summary>
 049        protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }
 50
 51        protected static JsonConverter<TElement> GetElementConverter(JsonTypeInfo elementTypeInfo)
 1452        {
 1453            return ((JsonTypeInfo<TElement>)elementTypeInfo).EffectiveConverter;
 1454        }
 55
 56        protected static JsonConverter<TElement> GetElementConverter(ref WriteStack state)
 057        {
 058            Debug.Assert(state.Current.JsonPropertyInfo is not null);
 059            return (JsonConverter<TElement>)state.Current.JsonPropertyInfo.EffectiveConverter;
 060        }
 61
 62        internal override bool OnTryRead(
 63            ref Utf8JsonReader reader,
 64            Type typeToConvert,
 65            JsonSerializerOptions options,
 66            scoped ref ReadStack state,
 67            [MaybeNullWhen(false)] out TCollection value)
 1868        {
 1869            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 1870            JsonTypeInfo elementTypeInfo = jsonTypeInfo.ElementTypeInfo!;
 71
 1872            if (!state.SupportContinuation && !state.Current.CanContainMetadata)
 073            {
 74                // Fast path that avoids maintaining state variables and dealing with preserved references.
 75
 076                if (reader.TokenType != JsonTokenType.StartArray)
 077                {
 078                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 79                }
 80
 081                CreateCollection(ref reader, ref state, options);
 82
 083                jsonTypeInfo.OnDeserializing?.Invoke(state.Current.ReturnValue!);
 84
 085                state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 086                JsonConverter<TElement> elementConverter = GetElementConverter(elementTypeInfo);
 087                if (elementConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling is null)
 088                {
 89                    // Fast path that avoids validation and extra indirection.
 090                    while (true)
 091                    {
 092                        reader.ReadWithVerify();
 093                        if (reader.TokenType == JsonTokenType.EndArray)
 094                        {
 095                            break;
 96                        }
 97
 98                        // Obtain the CLR value from the JSON and apply to the object.
 099                        TElement? element = elementConverter.Read(ref reader, elementConverter.Type, options);
 0100                        Add(element!, ref state);
 0101                    }
 0102                }
 103                else
 0104                {
 105                    // Process all elements.
 0106                    while (true)
 0107                    {
 0108                        reader.ReadWithVerify();
 0109                        if (reader.TokenType == JsonTokenType.EndArray)
 0110                        {
 0111                            break;
 112                        }
 113
 114                        // Get the value from the converter and add it.
 0115                        elementConverter.TryRead(ref reader, typeof(TElement), options, ref state, out TElement? element
 0116                        Add(element!, ref state);
 0117                    }
 0118                }
 0119            }
 120            else
 18121            {
 122                // Slower path that supports continuation and reading metadata.
 18123                if (state.Current.ObjectState == StackFrameObjectState.None)
 18124                {
 18125                    if (reader.TokenType == JsonTokenType.StartArray)
 14126                    {
 14127                        state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
 14128                    }
 4129                    else if (state.Current.CanContainMetadata)
 0130                    {
 0131                        if (reader.TokenType != JsonTokenType.StartObject)
 0132                        {
 0133                            ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 134                        }
 135
 0136                        state.Current.ObjectState = StackFrameObjectState.StartToken;
 0137                    }
 138                    else
 4139                    {
 4140                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 141                    }
 14142                }
 143
 144                // Handle the metadata properties.
 14145                if (state.Current.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
 0146                {
 0147                    if (!JsonSerializer.TryReadMetadata(this, jsonTypeInfo, ref reader, ref state))
 0148                    {
 0149                        value = default;
 0150                        return false;
 151                    }
 152
 0153                    if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
 0154                    {
 0155                        value = JsonSerializer.ResolveReferenceId<TCollection>(ref state);
 0156                        return true;
 157                    }
 158
 0159                    state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
 0160                }
 161
 162                // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
 14163                if (((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 || state.PolymorphicResolved
 14164                    state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStart
 14165                    ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
 0166                {
 0167                    Debug.Assert(!IsValueType);
 0168                    bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.Type!, option
 0169                    value = (TCollection)objectResult!;
 0170                    state.ExitPolymorphicConverter(success);
 0171                    return success;
 172                }
 173
 14174                if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
 14175                {
 14176                    if (state.Current.CanContainMetadata)
 0177                    {
 0178                        JsonSerializer.ValidateMetadataForArrayConverter(this, ref reader, ref state);
 0179                    }
 180
 14181                    CreateCollection(ref reader, ref state, options);
 182
 14183                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
 0184                    {
 0185                        Debug.Assert(state.ReferenceId is not null);
 0186                        Debug.Assert(options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve);
 0187                        Debug.Assert(state.Current.ReturnValue is TCollection);
 0188                        state.ReferenceResolver.AddReference(state.ReferenceId, state.Current.ReturnValue);
 0189                        state.ReferenceId = null;
 0190                    }
 191
 14192                    jsonTypeInfo.OnDeserializing?.Invoke(state.Current.ReturnValue!);
 193
 14194                    state.Current.ObjectState = StackFrameObjectState.CreatedObject;
 14195                }
 196
 14197                if (state.Current.ObjectState < StackFrameObjectState.ReadElements)
 14198                {
 14199                    JsonConverter<TElement> elementConverter = GetElementConverter(elementTypeInfo);
 14200                    state.Current.JsonPropertyInfo = elementTypeInfo.PropertyInfoForTypeInfo;
 201
 202                    // Process all elements.
 14203                    while (true)
 14204                    {
 14205                        if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
 14206                        {
 14207                            if (!reader.TryAdvanceWithOptionalReadAhead(elementConverter.RequiresReadAhead))
 0208                            {
 0209                                value = default;
 0210                                return false;
 211                            }
 212
 0213                            state.Current.PropertyState = StackFramePropertyState.ReadValue;
 0214                        }
 215
 0216                        if (state.Current.PropertyState < StackFramePropertyState.ReadValueIsEnd)
 0217                        {
 0218                            if (reader.TokenType == JsonTokenType.EndArray)
 0219                            {
 0220                                break;
 221                            }
 222
 0223                            state.Current.PropertyState = StackFramePropertyState.ReadValueIsEnd;
 0224                        }
 225
 0226                        if (state.Current.PropertyState < StackFramePropertyState.TryRead)
 0227                        {
 228                            // Get the value from the converter and add it.
 0229                            if (!elementConverter.TryRead(ref reader, typeof(TElement), options, ref state, out TElement
 0230                            {
 0231                                value = default;
 0232                                return false;
 233                            }
 234
 0235                            Add(element!, ref state);
 236
 237                            // No need to set PropertyState to TryRead since we're done with this element now.
 0238                            state.Current.EndElement();
 0239                        }
 0240                    }
 241
 0242                    state.Current.ObjectState = StackFrameObjectState.ReadElements;
 0243                }
 244
 0245                if (state.Current.ObjectState < StackFrameObjectState.EndToken)
 0246                {
 247                    // Array payload is nested inside a $values metadata property.
 0248                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Values) != 0)
 0249                    {
 0250                        if (!reader.Read())
 0251                        {
 0252                            value = default;
 0253                            return false;
 254                        }
 0255                    }
 256
 0257                    state.Current.ObjectState = StackFrameObjectState.EndToken;
 0258                }
 259
 0260                if (state.Current.ObjectState < StackFrameObjectState.EndTokenValidation)
 0261                {
 262                    // Array payload is nested inside a $values metadata property.
 0263                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Values) != 0)
 0264                    {
 0265                        if (reader.TokenType != JsonTokenType.EndObject)
 0266                        {
 0267                            Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 0268                            if (options.AllowOutOfOrderMetadataProperties)
 0269                            {
 0270                                Debug.Assert(JsonSerializer.IsMetadataPropertyName(reader.GetUnescapedSpan(), (state.Cur
 0271                                bool result = reader.TrySkipPartial(reader.CurrentDepth - 1); // skip to the end of the 
 0272                                Debug.Assert(result, "Metadata reader must have buffered all contents.");
 0273                                Debug.Assert(reader.TokenType is JsonTokenType.EndObject);
 0274                            }
 275                            else
 0276                            {
 0277                                ThrowHelper.ThrowJsonException_MetadataInvalidPropertyInArrayMetadata(ref state, typeToC
 278                            }
 0279                        }
 0280                    }
 0281                }
 0282            }
 283
 0284            ConvertCollection(ref state, options);
 0285            object returnValue = state.Current.ReturnValue!;
 0286            jsonTypeInfo.OnDeserialized?.Invoke(returnValue);
 0287            value = (TCollection)returnValue;
 288
 0289            return true;
 0290        }
 291
 292        internal override bool OnTryWrite(
 293            Utf8JsonWriter writer,
 294            TCollection value,
 295            JsonSerializerOptions options,
 296            ref WriteStack state)
 0297        {
 298            bool success;
 299
 0300            if (value is null)
 0301            {
 0302                writer.WriteNullValue();
 0303                success = true;
 0304            }
 305            else
 0306            {
 0307                JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 308
 0309                if (!state.Current.ProcessedStartToken)
 0310                {
 0311                    state.Current.ProcessedStartToken = true;
 312
 0313                    jsonTypeInfo.OnSerializing?.Invoke(value);
 314
 0315                    if (state.CurrentContainsMetadata && CanHaveMetadata)
 0316                    {
 0317                        state.Current.MetadataPropertyName = JsonSerializer.WriteMetadataForCollection(this, ref state, 
 0318                    }
 319
 320                    // Writing the start of the array must happen after any metadata
 0321                    writer.WriteStartArray();
 0322                    state.Current.JsonPropertyInfo = jsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
 0323                }
 324
 0325                success = OnWriteResume(writer, value, options, ref state);
 0326                if (success)
 0327                {
 0328                    if (!state.Current.ProcessedEndToken)
 0329                    {
 0330                        state.Current.ProcessedEndToken = true;
 0331                        writer.WriteEndArray();
 332
 0333                        if (state.Current.MetadataPropertyName != 0)
 0334                        {
 335                            // Write the EndObject for $values.
 0336                            writer.WriteEndObject();
 0337                        }
 0338                    }
 339
 0340                    jsonTypeInfo.OnSerialized?.Invoke(value);
 0341                }
 0342            }
 343
 0344            return success;
 0345        }
 346
 347        protected abstract bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, r
 348    }
 349}
 350