< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 323
Coverable lines: 323
Total lines: 510
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 138
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
OnTryRead(...)0%78780%
PopulatePropertiesFastPath(...)0%880%
OnTryWrite(...)0%46460%
ReadPropertyValue(...)0%440%
ReadAheadPropertyValue(...)0%220%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.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.Runtime.CompilerServices;
 8using System.Text.Json.Serialization.Metadata;
 9
 10namespace System.Text.Json.Serialization.Converters
 11{
 12    /// <summary>
 13    /// Default base class implementation of <cref>JsonObjectConverter{T}</cref>.
 14    /// </summary>
 15    /// <remarks>
 16    /// Aspects of the property reading and deserialization logic in this converter are
 17    /// duplicated in <see cref="FSharpUnionConverter{T}"/>. If any behavior is changed here
 18    /// (e.g. unmapped member handling, required properties, metadata processing),
 19    /// the union converter should also be updated for parity.
 20    /// </remarks>
 21    internal class ObjectDefaultConverter<T> : JsonObjectConverter<T> where T : notnull
 22    {
 023        internal override bool CanHaveMetadata => true;
 024        internal override bool SupportsCreateObjectDelegate => true;
 25
 26        internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, s
 027        {
 028            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 29
 30            object obj;
 31
 032            if (!state.SupportContinuation && !state.Current.CanContainMetadata)
 033            {
 34                // Fast path that avoids maintaining state variables and dealing with preserved references.
 35
 036                if (reader.TokenType != JsonTokenType.StartObject)
 037                {
 038                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 39                }
 40
 041                if (state.ParentProperty?.TryGetPrePopulatedValue(ref state) == true)
 042                {
 043                    obj = state.Current.ReturnValue!;
 044                }
 45                else
 046                {
 047                    if (jsonTypeInfo.CreateObject is null)
 048                    {
 049                        ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(jsonTypeInfo, ref reader, ref st
 50                    }
 51
 052                    obj = jsonTypeInfo.CreateObject();
 053                }
 54
 055                PopulatePropertiesFastPath(obj, jsonTypeInfo, options, ref reader, ref state);
 056                Debug.Assert(obj is not null);
 057                value = (T)obj;
 058                return true;
 59            }
 60            else
 061            {
 62                // Slower path that supports continuation and reading metadata.
 63
 064                if (state.Current.ObjectState == StackFrameObjectState.None)
 065                {
 066                    if (reader.TokenType != JsonTokenType.StartObject)
 067                    {
 068                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type);
 69                    }
 70
 071                    state.Current.ObjectState = StackFrameObjectState.StartToken;
 072                }
 73
 74                // Handle the metadata properties.
 075                if (state.Current.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
 076                {
 077                    if (!JsonSerializer.TryReadMetadata(this, jsonTypeInfo, ref reader, ref state))
 078                    {
 079                        value = default;
 080                        return false;
 81                    }
 82
 083                    if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
 084                    {
 085                        value = JsonSerializer.ResolveReferenceId<T>(ref state);
 086                        return true;
 87                    }
 88
 089                    state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
 090                }
 91
 92                // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
 093                if (((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 || state.PolymorphicResolved
 094                    state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStart
 095                    ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
 096                {
 097                    Debug.Assert(!IsValueType);
 098                    bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.Type!, option
 099                    value = (T)objectResult!;
 0100                    state.ExitPolymorphicConverter(success);
 0101                    return success;
 102                }
 103
 0104                if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
 0105                {
 0106                    if (state.Current.CanContainMetadata)
 0107                    {
 0108                        JsonSerializer.ValidateMetadataForObjectConverter(ref state);
 0109                    }
 110
 0111                    if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
 0112                    {
 0113                        value = JsonSerializer.ResolveReferenceId<T>(ref state);
 0114                        return true;
 115                    }
 116
 0117                    if (state.ParentProperty?.TryGetPrePopulatedValue(ref state) == true)
 0118                    {
 0119                        obj = state.Current.ReturnValue!;
 0120                    }
 121                    else
 0122                    {
 0123                        if (jsonTypeInfo.CreateObject is null)
 0124                        {
 0125                            ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(jsonTypeInfo, ref reader, re
 126                        }
 127
 0128                        obj = jsonTypeInfo.CreateObject();
 0129                    }
 130
 0131                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
 0132                    {
 0133                        Debug.Assert(state.ReferenceId is not null);
 0134                        Debug.Assert(options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve);
 0135                        state.ReferenceResolver.AddReference(state.ReferenceId, obj);
 0136                        state.ReferenceId = null;
 0137                    }
 138
 0139                    jsonTypeInfo.OnDeserializing?.Invoke(obj);
 140
 0141                    state.Current.ReturnValue = obj;
 0142                    state.Current.ObjectState = StackFrameObjectState.CreatedObject;
 0143                    state.Current.InitializePropertiesValidationState(jsonTypeInfo);
 0144                }
 145                else
 0146                {
 0147                    obj = state.Current.ReturnValue!;
 0148                    Debug.Assert(obj is not null);
 0149                }
 150
 151                // Process all properties.
 0152                while (true)
 0153                {
 154                    // Determine the property.
 0155                    if (state.Current.PropertyState == StackFramePropertyState.None)
 0156                    {
 0157                        if (!reader.Read())
 0158                        {
 0159                            state.Current.ReturnValue = obj;
 0160                            value = default;
 0161                            return false;
 162                        }
 163
 0164                        state.Current.PropertyState = StackFramePropertyState.ReadName;
 0165                    }
 166
 167                    JsonPropertyInfo jsonPropertyInfo;
 168
 0169                    if (state.Current.PropertyState < StackFramePropertyState.Name)
 0170                    {
 0171                        JsonTokenType tokenType = reader.TokenType;
 0172                        if (tokenType == JsonTokenType.EndObject)
 0173                        {
 0174                            break;
 175                        }
 176
 177                        // Read method would have thrown if otherwise.
 0178                        Debug.Assert(tokenType == JsonTokenType.PropertyName);
 179
 0180                        jsonTypeInfo.ValidateCanBeUsedForPropertyMetadataSerialization();
 0181                        ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader,
 0182                        if (isAlreadyReadMetadataProperty)
 0183                        {
 0184                            Debug.Assert(options.AllowOutOfOrderMetadataProperties);
 0185                            reader.SkipWithVerify();
 0186                            state.Current.EndProperty();
 0187                            continue;
 188                        }
 189
 0190                        jsonPropertyInfo = JsonSerializer.LookupProperty(
 0191                            obj,
 0192                            unescapedPropertyName,
 0193                            ref state,
 0194                            options,
 0195                            out bool useExtensionProperty);
 196
 0197                        state.Current.UseExtensionProperty = useExtensionProperty;
 0198                        state.Current.PropertyState = StackFramePropertyState.Name;
 0199                    }
 200                    else
 0201                    {
 0202                        Debug.Assert(state.Current.JsonPropertyInfo is not null);
 0203                        jsonPropertyInfo = state.Current.JsonPropertyInfo!;
 0204                    }
 205
 0206                    if (state.Current.PropertyState < StackFramePropertyState.ReadValue)
 0207                    {
 0208                        if (!jsonPropertyInfo.CanDeserializeOrPopulate)
 0209                        {
 0210                            if (!reader.TrySkipPartial(targetDepth: state.Current.OriginalDepth + 1))
 0211                            {
 0212                                state.Current.ReturnValue = obj;
 0213                                value = default;
 0214                                return false;
 215                            }
 216
 0217                            state.Current.EndProperty();
 0218                            continue;
 219                        }
 220
 0221                        if (!ReadAheadPropertyValue(ref state, ref reader, jsonPropertyInfo))
 0222                        {
 0223                            state.Current.ReturnValue = obj;
 0224                            value = default;
 0225                            return false;
 226                        }
 227
 0228                        state.Current.PropertyState = StackFramePropertyState.ReadValue;
 0229                    }
 230
 0231                    if (state.Current.PropertyState < StackFramePropertyState.TryRead)
 0232                    {
 233                        // Obtain the CLR value from the JSON and set the member.
 0234                        if (!state.Current.UseExtensionProperty)
 0235                        {
 0236                            if (!jsonPropertyInfo.ReadJsonAndSetMember(obj, ref state, ref reader))
 0237                            {
 0238                                state.Current.ReturnValue = obj;
 0239                                value = default;
 0240                                return false;
 241                            }
 0242                        }
 243                        else
 0244                        {
 0245                            if (!jsonPropertyInfo.ReadJsonAndAddExtensionProperty(obj, ref state, ref reader))
 0246                            {
 247                                // No need to set 'value' here since JsonElement must be read in full.
 0248                                state.Current.ReturnValue = obj;
 0249                                value = default;
 0250                                return false;
 251                            }
 0252                        }
 253
 0254                        state.Current.EndProperty();
 0255                    }
 0256                }
 0257            }
 258
 0259            jsonTypeInfo.OnDeserialized?.Invoke(obj);
 0260            state.Current.ValidateAllRequiredPropertiesAreRead(jsonTypeInfo);
 261
 262            // Unbox
 0263            Debug.Assert(obj is not null);
 0264            value = (T)obj;
 265
 266            // Check if we are trying to update the UTF-8 property cache.
 0267            if (state.Current.PropertyRefCacheBuilder is not null)
 0268            {
 0269                jsonTypeInfo.UpdateUtf8PropertyCache(ref state.Current);
 0270            }
 271
 0272            return true;
 0273        }
 274
 275        // This method is using aggressive inlining to avoid extra stack frame for deep object graphs.
 276        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 277        internal static void PopulatePropertiesFastPath(object obj, JsonTypeInfo jsonTypeInfo, JsonSerializerOptions opt
 0278        {
 0279            jsonTypeInfo.OnDeserializing?.Invoke(obj);
 0280            state.Current.InitializePropertiesValidationState(jsonTypeInfo);
 281
 282            // Process all properties.
 0283            while (true)
 0284            {
 285                // Read the property name or EndObject.
 0286                reader.ReadWithVerify();
 287
 0288                JsonTokenType tokenType = reader.TokenType;
 289
 0290                if (tokenType == JsonTokenType.EndObject)
 0291                {
 0292                    break;
 293                }
 294
 295                // Read method would have thrown if otherwise.
 0296                Debug.Assert(tokenType == JsonTokenType.PropertyName);
 297
 0298                ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options
 0299                Debug.Assert(!isAlreadyReadMetadataProperty, "Only possible for types that can read metadata, which do n
 300
 0301                jsonTypeInfo.ValidateCanBeUsedForPropertyMetadataSerialization();
 0302                JsonPropertyInfo jsonPropertyInfo = JsonSerializer.LookupProperty(
 0303                    obj,
 0304                    unescapedPropertyName,
 0305                    ref state,
 0306                    options,
 0307                    out bool useExtensionProperty);
 308
 0309                ReadPropertyValue(obj, ref state, ref reader, jsonPropertyInfo, useExtensionProperty);
 0310            }
 311
 0312            jsonTypeInfo.OnDeserialized?.Invoke(obj);
 0313            state.Current.ValidateAllRequiredPropertiesAreRead(jsonTypeInfo);
 314
 315            // Check if we are trying to update the UTF-8 property cache.
 0316            if (state.Current.PropertyRefCacheBuilder is not null)
 0317            {
 0318                jsonTypeInfo.UpdateUtf8PropertyCache(ref state.Current);
 0319            }
 0320        }
 321
 322        internal sealed override bool OnTryWrite(
 323            Utf8JsonWriter writer,
 324            T value,
 325            JsonSerializerOptions options,
 326            ref WriteStack state)
 0327        {
 0328            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
 0329            jsonTypeInfo.ValidateCanBeUsedForPropertyMetadataSerialization();
 330
 0331            object obj = value; // box once
 332
 0333            if (!state.SupportContinuation)
 0334            {
 0335                jsonTypeInfo.OnSerializing?.Invoke(obj);
 336
 0337                writer.WriteStartObject();
 338
 0339                if (state.CurrentContainsMetadata && CanHaveMetadata)
 0340                {
 0341                    JsonSerializer.WriteMetadataForObject(this, ref state, writer);
 0342                }
 343
 0344                foreach (JsonPropertyInfo jsonPropertyInfo in jsonTypeInfo.PropertyCache)
 0345                {
 0346                    if (jsonPropertyInfo.CanSerialize)
 0347                    {
 348                        // Remember the current property for JsonPath support if an exception is thrown.
 0349                        state.Current.JsonPropertyInfo = jsonPropertyInfo;
 0350                        state.Current.NumberHandling = jsonPropertyInfo.EffectiveNumberHandling;
 351
 0352                        bool success = jsonPropertyInfo.GetMemberAndWriteJson(obj, ref state, writer);
 353                        // Converters only return 'false' when out of data which is not possible in fast path.
 0354                        Debug.Assert(success);
 355
 0356                        state.Current.EndProperty();
 0357                    }
 0358                }
 359
 360                // Write extension data after the normal properties.
 0361                JsonPropertyInfo? extensionDataProperty = jsonTypeInfo.ExtensionDataProperty;
 0362                if (extensionDataProperty?.CanSerialize == true)
 0363                {
 364                    // Remember the current property for JsonPath support if an exception is thrown.
 0365                    state.Current.JsonPropertyInfo = extensionDataProperty;
 0366                    state.Current.NumberHandling = extensionDataProperty.EffectiveNumberHandling;
 367
 0368                    bool success = extensionDataProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer);
 0369                    Debug.Assert(success);
 370
 0371                    state.Current.EndProperty();
 0372                }
 373
 0374                writer.WriteEndObject();
 0375            }
 376            else
 0377            {
 0378                if (!state.Current.ProcessedStartToken)
 0379                {
 0380                    writer.WriteStartObject();
 381
 0382                    if (state.CurrentContainsMetadata && CanHaveMetadata)
 0383                    {
 0384                        JsonSerializer.WriteMetadataForObject(this, ref state, writer);
 0385                    }
 386
 0387                    jsonTypeInfo.OnSerializing?.Invoke(obj);
 388
 0389                    state.Current.ProcessedStartToken = true;
 0390                }
 391
 0392                ReadOnlySpan<JsonPropertyInfo> propertyCache = jsonTypeInfo.PropertyCache;
 0393                while (state.Current.EnumeratorIndex < propertyCache.Length)
 0394                {
 0395                    JsonPropertyInfo jsonPropertyInfo = propertyCache[state.Current.EnumeratorIndex];
 0396                    if (jsonPropertyInfo.CanSerialize)
 0397                    {
 0398                        state.Current.JsonPropertyInfo = jsonPropertyInfo;
 0399                        state.Current.NumberHandling = jsonPropertyInfo.EffectiveNumberHandling;
 400
 0401                        if (!jsonPropertyInfo.GetMemberAndWriteJson(obj!, ref state, writer))
 0402                        {
 0403                            Debug.Assert(jsonPropertyInfo.EffectiveConverter.ConverterStrategy != ConverterStrategy.Valu
 0404                            return false;
 405                        }
 406
 0407                        state.Current.EndProperty();
 0408                        state.Current.EnumeratorIndex++;
 409
 0410                        if (ShouldFlush(ref state, writer))
 0411                        {
 0412                            return false;
 413                        }
 0414                    }
 415                    else
 0416                    {
 0417                        state.Current.EnumeratorIndex++;
 0418                    }
 0419                }
 420
 421                // Write extension data after the normal properties.
 0422                if (state.Current.EnumeratorIndex == propertyCache.Length)
 0423                {
 0424                    JsonPropertyInfo? extensionDataProperty = jsonTypeInfo.ExtensionDataProperty;
 0425                    if (extensionDataProperty?.CanSerialize == true)
 0426                    {
 427                        // Remember the current property for JsonPath support if an exception is thrown.
 0428                        state.Current.JsonPropertyInfo = extensionDataProperty;
 0429                        state.Current.NumberHandling = extensionDataProperty.EffectiveNumberHandling;
 430
 0431                        if (!extensionDataProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer))
 0432                        {
 0433                            return false;
 434                        }
 435
 0436                        state.Current.EndProperty();
 0437                        state.Current.EnumeratorIndex++;
 438
 0439                        if (ShouldFlush(ref state, writer))
 0440                        {
 0441                            return false;
 442                        }
 0443                    }
 444                    else
 0445                    {
 0446                        state.Current.EnumeratorIndex++;
 0447                    }
 0448                }
 449
 0450                if (!state.Current.ProcessedEndToken)
 0451                {
 0452                    state.Current.ProcessedEndToken = true;
 0453                    writer.WriteEndObject();
 0454                }
 0455            }
 456
 0457            jsonTypeInfo.OnSerialized?.Invoke(obj);
 458
 0459            return true;
 0460        }
 461
 462        // AggressiveInlining since this method is only called from two locations and is on a hot path.
 463        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 464        protected static void ReadPropertyValue(
 465            object obj,
 466            scoped ref ReadStack state,
 467            ref Utf8JsonReader reader,
 468            JsonPropertyInfo jsonPropertyInfo,
 469            bool useExtensionProperty)
 0470        {
 471            // Skip the property if not found.
 0472            if (!jsonPropertyInfo.CanDeserializeOrPopulate)
 0473            {
 474                // The Utf8JsonReader.Skip() method will fail fast if it detects that we're reading
 475                // from a partially read buffer, regardless of whether the next value is available.
 476                // This can result in erroneous failures in cases where a custom converter is calling
 477                // into a built-in converter (cf. https://github.com/dotnet/runtime/issues/74108).
 478                // For this reason we need to call the TrySkip() method instead -- the serializer
 479                // should guarantee sufficient read-ahead has been performed for the current object.
 0480                bool success = reader.TrySkip();
 0481                Debug.Assert(success, "Serializer should guarantee sufficient read-ahead has been done.");
 0482            }
 483            else
 0484            {
 485                // Set the property value.
 0486                reader.ReadWithVerify();
 487
 0488                if (!useExtensionProperty)
 0489                {
 0490                    jsonPropertyInfo.ReadJsonAndSetMember(obj, ref state, ref reader);
 0491                }
 492                else
 0493                {
 0494                    jsonPropertyInfo.ReadJsonAndAddExtensionProperty(obj, ref state, ref reader);
 0495                }
 0496            }
 497
 498            // Ensure any exception thrown in the next read does not have a property in its JsonPath.
 0499            state.Current.EndProperty();
 0500        }
 501
 502        protected static bool ReadAheadPropertyValue(scoped ref ReadStack state, ref Utf8JsonReader reader, JsonProperty
 0503        {
 504            // Extension properties can use the JsonElement converter and thus require read-ahead.
 0505            bool requiresReadAhead = jsonPropertyInfo.EffectiveConverter.RequiresReadAhead || state.Current.UseExtension
 0506            return reader.TryAdvanceWithOptionalReadAhead(requiresReadAhead);
 0507        }
 508    }
 509}
 510