|
// 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 != 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 != 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 == null || (isReadOnlyDictionary && extensionData != 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 == 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 != 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 != null);
jsonPropertyInfo.Set(obj, extensionData);
return;
}
else if (jsonPropertyInfo.PropertyType == typeof(IReadOnlyDictionary<string, JsonElement>))
{
if (extensionData != 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 != null);
jsonPropertyInfo.Set(obj, extensionData);
return;
}
else
{
ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(jsonPropertyInfo.PropertyType);
}
}
extensionData = createObjectForExtensionDataProp();
Debug.Assert(jsonPropertyInfo.Set != 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.
}
}
}
|