| File: System\Text\Json\Serialization\JsonSerializer.Read.HandlePropertyName.cs | Web Access |
| Project: src\runtime\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text.Json.Reflection; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json { public static partial class JsonSerializer { /// <summary> /// Lookup the property given its name (obtained from the reader) and return it. /// Also sets state.Current.JsonPropertyInfo to a non-null value. /// </summary> internal static JsonPropertyInfo LookupProperty( object? obj, ReadOnlySpan<byte> unescapedPropertyName, ref ReadStack state, JsonSerializerOptions options, out bool useExtensionProperty, bool createExtensionProperty = true) { JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo; useExtensionProperty = false; JsonPropertyInfo? jsonPropertyInfo = jsonTypeInfo.GetProperty( unescapedPropertyName, ref state.Current, out byte[] utf8PropertyName); // Increment PropertyIndex so GetProperty() checks the next property first when called again. state.Current.PropertyIndex++; // For case insensitive and missing property support of JsonPath, remember the value on the temporary stack. state.Current.JsonPropertyName = utf8PropertyName; // Handle missing properties if (jsonPropertyInfo is null) { if (jsonTypeInfo.EffectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow) { Debug.Assert(jsonTypeInfo.ExtensionDataProperty is null, "jsonTypeInfo.Configure() should have caught conflicting configuration."); string stringPropertyName = Encoding.UTF8.GetString(unescapedPropertyName); ThrowHelper.ThrowJsonException_UnmappedJsonProperty(jsonTypeInfo.Type, stringPropertyName); } // Determine if we should use the extension property. if (jsonTypeInfo.ExtensionDataProperty is JsonPropertyInfo { HasGetter: true, HasSetter: true } dataExtProperty) { state.Current.JsonPropertyNameAsString = Encoding.UTF8.GetString(unescapedPropertyName); if (createExtensionProperty) { Debug.Assert(obj is not null, "obj is null"); CreateExtensionDataProperty(obj, dataExtProperty, options); } jsonPropertyInfo = dataExtProperty; useExtensionProperty = true; } else { // Populate with a placeholder value required by JsonPath calculations jsonPropertyInfo = JsonPropertyInfo.s_missingProperty; } } state.Current.JsonPropertyInfo = jsonPropertyInfo; state.Current.NumberHandling = jsonPropertyInfo.EffectiveNumberHandling; return jsonPropertyInfo; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan<byte> GetPropertyName( scoped ref ReadStack state, ref Utf8JsonReader reader, JsonSerializerOptions options, out bool isAlreadyReadMetadataProperty) { ReadOnlySpan<byte> propertyName = reader.GetUnescapedSpan(); isAlreadyReadMetadataProperty = false; if (state.Current.CanContainMetadata) { if (IsMetadataPropertyName(propertyName, state.Current.BaseJsonTypeInfo.PolymorphicTypeResolver)) { if (options.AllowOutOfOrderMetadataProperties) { isAlreadyReadMetadataProperty = true; } else { ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); } } } return propertyName; } internal static void CreateExtensionDataProperty( object obj, JsonPropertyInfo jsonPropertyInfo, JsonSerializerOptions options) { Debug.Assert(jsonPropertyInfo is not null); object? extensionData = jsonPropertyInfo.GetValueAsObject(obj); // For IReadOnlyDictionary, if there's an existing non-null instance, we need to create a new mutable // Dictionary seeded with the existing contents so we can add the deserialized extension data to it. bool isReadOnlyDictionary = jsonPropertyInfo.PropertyType == typeof(IReadOnlyDictionary<string, object>) || jsonPropertyInfo.PropertyType == typeof(IReadOnlyDictionary<string, JsonElement>); if (extensionData is null || (isReadOnlyDictionary && extensionData is not null)) { // Create the appropriate dictionary type. We already verified the types. #if DEBUG Type? underlyingIDictionaryType = jsonPropertyInfo.PropertyType.GetCompatibleGenericInterface(typeof(IDictionary<,>)) ?? jsonPropertyInfo.PropertyType.GetCompatibleGenericInterface(typeof(IReadOnlyDictionary<,>)); Debug.Assert(underlyingIDictionaryType is not null); Type[] genericArgs = underlyingIDictionaryType.GetGenericArguments(); Debug.Assert(underlyingIDictionaryType.IsGenericType); Debug.Assert(genericArgs.Length == 2); Debug.Assert(genericArgs[0].UnderlyingSystemType == typeof(string)); Debug.Assert( genericArgs[1].UnderlyingSystemType == JsonTypeInfo.ObjectType || genericArgs[1].UnderlyingSystemType == typeof(JsonElement) || genericArgs[1].UnderlyingSystemType == typeof(Nodes.JsonNode)); #endif Func<object>? createObjectForExtensionDataProp = jsonPropertyInfo.JsonTypeInfo.CreateObject ?? jsonPropertyInfo.JsonTypeInfo.CreateObjectForExtensionDataProperty; if (createObjectForExtensionDataProp is null) { // Avoid a reference to the JsonNode type for trimming if (jsonPropertyInfo.PropertyType.FullName == JsonTypeInfo.JsonObjectTypeName) { ThrowHelper.ThrowInvalidOperationException_NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty(); } // For IReadOnlyDictionary<string, object> or IReadOnlyDictionary<string, JsonElement> interface types, // create a Dictionary<TKey, TValue> instance seeded with any existing contents. else if (jsonPropertyInfo.PropertyType == typeof(IReadOnlyDictionary<string, object>)) { if (extensionData is not null) { var existing = (IReadOnlyDictionary<string, object>)extensionData; var newDict = new Dictionary<string, object>(); foreach (KeyValuePair<string, object> kvp in existing) { newDict[kvp.Key] = kvp.Value; } extensionData = newDict; } else { extensionData = new Dictionary<string, object>(); } Debug.Assert(jsonPropertyInfo.Set is not null); jsonPropertyInfo.Set(obj, extensionData); return; } else if (jsonPropertyInfo.PropertyType == typeof(IReadOnlyDictionary<string, JsonElement>)) { if (extensionData is not null) { var existing = (IReadOnlyDictionary<string, JsonElement>)extensionData; var newDict = new Dictionary<string, JsonElement>(); foreach (KeyValuePair<string, JsonElement> kvp in existing) { newDict[kvp.Key] = kvp.Value; } extensionData = newDict; } else { extensionData = new Dictionary<string, JsonElement>(); } Debug.Assert(jsonPropertyInfo.Set is not null); jsonPropertyInfo.Set(obj, extensionData); return; } else { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(jsonPropertyInfo.PropertyType); } } extensionData = createObjectForExtensionDataProp(); Debug.Assert(jsonPropertyInfo.Set is not null); jsonPropertyInfo.Set(obj, extensionData); } // We don't add the value to the dictionary here because we need to support the read-ahead functionality for Streams. } } }