* Remove types deriving from JsonTypeInfo<T> and mark as sealed.
* Rename the metadata resolution files.
* Address feedback
* Only sort properties if necessary.
castingRequiredForProps = false;
serializableProperties = new Dictionary<string, PropertyGenerationSpec>();
- Dictionary<string, PropertyGenerationSpec>? ignoredMembers = null;
+ HashSet<string>? ignoredMembers = null;
for (int i = 0; i < PropertyGenSpecList.Count; i++)
{
other.ClrName == memberName ||
// Was a property with the same CLR name ignored? That property hid the current property,
// thus, if it was ignored, the current property should be ignored too.
- ignoredMembers?.ContainsKey(memberName) == true;
+ ignoredMembers?.Contains(memberName) == true;
}
else
{
if (propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
{
- (ignoredMembers ??= new()).Add(memberName, propGenSpec);
+ (ignoredMembers ??= new()).Add(memberName);
}
}
{
public static System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver Combine(params System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver?[] resolvers) { throw null; }
}
- public abstract partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
+ public sealed partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
{
internal JsonTypeInfo() { }
public new System.Func<T>? CreateObject { get { throw null; } set { } }
<Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
<Compile Include="System\Text\Json\Serialization\JsonUnknownDerivedTypeHandling.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\DefaultJsonTypeInfoResolver.Converters.cs" />
+ <Compile Include="System\Text\Json\Serialization\Metadata\DefaultJsonTypeInfoResolver.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\DefaultJsonTypeInfoResolver.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\IJsonTypeInfoResolver.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonDerivedType.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoOfT.WriteHelpers.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolver.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoKind.cs" />
- <Compile Include="System\Text\Json\Serialization\Metadata\CustomJsonTypeInfoOfT.cs" />
<Compile Include="System\Text\Json\Serialization\PolymorphicSerializationState.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriterCache.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceEqualsWrapper.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonCollectionInfoValuesOfTCollection.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Converters.cs" />
+ <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonObjectInfoValuesOfT.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonParameterInfoValues.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonPropertyInfo.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonPropertyInfoOfT.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonPropertyInfoValuesOfT.cs" />
- <Compile Include="System\Text\Json\Serialization\Metadata\SourceGenJsonTypeInfoOfT.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoOfT.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfo.Cache.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfo.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ParameterRef.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\PolymorphicTypeResolver.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\PropertyRef.cs" />
- <Compile Include="System\Text\Json\Serialization\Metadata\ReflectionJsonTypeInfoOfT.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.Cache.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitMemberAccessor.cs" />
throw new InvalidOperationException();
}
- [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
- internal virtual JsonTypeInfo CreateReflectionJsonTypeInfo(JsonSerializerOptions options)
- {
- Debug.Fail("Should not be reachable.");
-
- throw new InvalidOperationException();
- }
-
- internal virtual JsonTypeInfo CreateCustomJsonTypeInfo(JsonSerializerOptions options)
+ internal virtual JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions options)
{
Debug.Fail("Should not be reachable.");
private protected override ConverterStrategy GetDefaultConverterStrategy() => ConverterStrategy.Value;
- [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
- internal sealed override JsonTypeInfo CreateReflectionJsonTypeInfo(JsonSerializerOptions options)
+ internal sealed override JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions options)
{
- return new ReflectionJsonTypeInfo<T>(this, options);
- }
-
- internal sealed override JsonTypeInfo CreateCustomJsonTypeInfo(JsonSerializerOptions options)
- {
- return new CustomJsonTypeInfo<T>(this, options);
+ return new JsonTypeInfo<T>(this, options);
}
internal sealed override JsonParameterInfo CreateJsonParameterInfo()
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics.CodeAnalysis;
-
-namespace System.Text.Json.Serialization.Metadata
-{
- /// <summary>
- /// Creates and initializes serialization metadata for a type.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- internal sealed class CustomJsonTypeInfo<T> : JsonTypeInfo<T>
- {
- /// <summary>
- /// Creates serialization metadata for a type using a simple converter.
- /// </summary>
- internal CustomJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
- : base(converter, options)
- {
- }
-
- internal override JsonParameterInfoValues[] GetParameterInfoValues()
- {
- // Parameterized constructors not supported yet for custom types
- return Array.Empty<JsonParameterInfoValues>();
- }
- }
-}
namespace System.Text.Json.Serialization.Metadata
{
- /// <summary>
- /// Provides JSON serialization-related metadata about a type.
- /// </summary>
- internal sealed class ReflectionJsonTypeInfo<T> : JsonTypeInfo<T>
+ public partial class DefaultJsonTypeInfoResolver
{
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
- : base(converter, options)
+ private static JsonTypeInfo CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
{
- NumberHandling = GetNumberHandlingForType(Type);
- if (Kind == JsonTypeInfoKind.Object)
+ JsonTypeInfo typeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, converter, options);
+ typeInfo.NumberHandling = GetNumberHandlingForType(typeInfo.Type);
+ if (typeInfo.Kind == JsonTypeInfoKind.Object)
{
- UnmappedMemberHandling = GetUnmappedMemberHandling(Type);
+ typeInfo.UnmappedMemberHandling = GetUnmappedMemberHandling(typeInfo.Type);
}
- PopulatePolymorphismMetadata();
- MapInterfaceTypesToCallbacks();
+ typeInfo.PopulatePolymorphismMetadata();
+ typeInfo.MapInterfaceTypesToCallbacks();
- Func<object>? createObject = JsonSerializerOptions.MemberAccessorStrategy.CreateConstructor(typeof(T));
- SetCreateObjectIfCompatible(createObject);
- CreateObjectForExtensionDataProperty = createObject;
+ Func<object>? createObject = JsonSerializerOptions.MemberAccessorStrategy.CreateConstructor(typeInfo.Type);
+ typeInfo.SetCreateObjectIfCompatible(createObject);
+ typeInfo.CreateObjectForExtensionDataProperty = createObject;
+
+ if (typeInfo.Kind is JsonTypeInfoKind.Object)
+ {
+ PopulateProperties(typeInfo);
+
+ if (converter.ConstructorIsParameterized)
+ {
+ PopulateParameterInfoValues(typeInfo);
+ }
+ }
// Plug in any converter configuration -- should be run last.
- converter.ConfigureJsonTypeInfo(this, options);
- converter.ConfigureJsonTypeInfoUsingReflection(this, options);
+ converter.ConfigureJsonTypeInfo(typeInfo, options);
+ converter.ConfigureJsonTypeInfoUsingReflection(typeInfo, options);
+ return typeInfo;
}
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
- Justification = "The ctor is marked RequiresUnreferencedCode.")]
- internal override void LateAddProperties()
+ [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+ [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+ private static void PopulateProperties(JsonTypeInfo typeInfo)
{
- Debug.Assert(!IsConfigured);
- Debug.Assert(PropertyCache is null);
-
- if (Kind != JsonTypeInfoKind.Object)
- {
- return;
- }
+ Debug.Assert(!typeInfo.IsReadOnly);
+ Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
// Compiler adds RequiredMemberAttribute to type if any of the members is marked with 'required' keyword.
// SetsRequiredMembersAttribute means that all required members are assigned by constructor and therefore there is no enforcement
bool shouldCheckMembersForRequiredMemberAttribute =
- typeof(T).HasRequiredMemberAttribute()
- && !(Converter.ConstructorInfo?.HasSetsRequiredMembersAttribute() ?? false);
+ typeInfo.Type.HasRequiredMemberAttribute()
+ && !(typeInfo.Converter.ConstructorInfo?.HasSetsRequiredMembersAttribute() ?? false);
- bool propertyOrderSpecified = false;
- Dictionary<string, JsonPropertyInfo>? ignoredMembers = null;
+ JsonTypeInfo.PropertyHierarchyResolutionState state = new();
// Walk the type hierarchy starting from the current type up to the base type(s)
- foreach (Type currentType in Type.GetSortedTypeHierarchy())
+ foreach (Type currentType in typeInfo.Type.GetSortedTypeHierarchy())
{
- if (currentType == ObjectType)
+ if (currentType == JsonTypeInfo.ObjectType)
{
// Don't process any members for typeof(object)
break;
}
AddMembersDeclaredBySuperType(
+ typeInfo,
currentType,
shouldCheckMembersForRequiredMemberAttribute,
- ref propertyOrderSpecified,
- ref ignoredMembers);
+ ref state);
}
- Debug.Assert(PropertyCache != null);
-
- if (propertyOrderSpecified)
+ if (state.IsPropertyOrderSpecified)
{
- PropertyCache.List.StableSortByKey(static p => p.Value.Order);
+ typeInfo.SortProperties();
}
}
- [UnconditionalSuppressMessage("Trimming", "IL2070:UnrecognizedReflectionPattern",
- Justification = "The ctor is marked RequiresUnreferencedCode.")]
- private void AddMembersDeclaredBySuperType(
+ [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+ [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+ private static void AddMembersDeclaredBySuperType(
+ JsonTypeInfo typeInfo,
Type currentType,
bool shouldCheckMembersForRequiredMemberAttribute,
- ref bool propertyOrderSpecified,
- ref Dictionary<string, JsonPropertyInfo>? ignoredMembers)
+ ref JsonTypeInfo.PropertyHierarchyResolutionState state)
{
- Debug.Assert(!IsConfigured);
- Debug.Assert(currentType.IsAssignableFrom(Type));
+ Debug.Assert(!typeInfo.IsReadOnly);
+ Debug.Assert(currentType.IsAssignableFrom(typeInfo.Type));
const BindingFlags BindingFlags =
BindingFlags.Instance |
PropertyInfo[] properties = currentType.GetProperties(BindingFlags);
- // PropertyCache is not accessed by other threads until the current JsonTypeInfo instance
- // is finished initializing and added to the cache in JsonSerializerOptions.
- // Default 'capacity' to the common non-polymorphic + property case.
- PropertyCache ??= CreatePropertyCache(capacity: properties.Length);
-
foreach (PropertyInfo propertyInfo in properties)
{
string propertyName = propertyInfo.Name;
// Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d.
if (propertyInfo.GetIndexParameters().Length > 0 ||
- PropertyIsOverriddenAndIgnored(propertyName, propertyInfo.PropertyType, propertyInfo.IsVirtual(), ignoredMembers))
+ PropertyIsOverriddenAndIgnored(propertyName, propertyInfo.PropertyType, propertyInfo.IsVirtual(), state.IgnoredProperties))
{
continue;
}
if (propertyInfo.GetMethod?.IsPublic == true ||
propertyInfo.SetMethod?.IsPublic == true)
{
- CacheMember(
+ AddMember(
+ typeInfo,
typeToConvert: propertyInfo.PropertyType,
memberInfo: propertyInfo,
- ref propertyOrderSpecified,
- ref ignoredMembers,
- shouldCheckMembersForRequiredMemberAttribute);
+ shouldCheckMembersForRequiredMemberAttribute,
+ ref state);
}
else
{
{
string fieldName = fieldInfo.Name;
- if (PropertyIsOverriddenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers))
+ if (PropertyIsOverriddenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, state.IgnoredProperties))
{
continue;
}
if (fieldInfo.IsPublic)
{
- if (hasJsonInclude || Options.IncludeFields)
+ if (hasJsonInclude || typeInfo.Options.IncludeFields)
{
- CacheMember(
+ AddMember(
+ typeInfo,
typeToConvert: fieldInfo.FieldType,
memberInfo: fieldInfo,
- ref propertyOrderSpecified,
- ref ignoredMembers,
- shouldCheckMembersForRequiredMemberAttribute);
+ shouldCheckMembersForRequiredMemberAttribute,
+ ref state);
}
}
else
}
}
- private void CacheMember(
+ [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+ [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+ private static void AddMember(
+ JsonTypeInfo typeInfo,
Type typeToConvert,
MemberInfo memberInfo,
- ref bool propertyOrderSpecified,
- ref Dictionary<string, JsonPropertyInfo>? ignoredMembers,
- bool shouldCheckForRequiredKeyword)
+ bool shouldCheckForRequiredKeyword,
+ ref JsonTypeInfo.PropertyHierarchyResolutionState state)
{
- JsonPropertyInfo? jsonPropertyInfo = CreateProperty(typeToConvert, memberInfo, Options, shouldCheckForRequiredKeyword);
+ JsonPropertyInfo? jsonPropertyInfo = CreatePropertyInfo(typeInfo, typeToConvert, memberInfo, typeInfo.Options, shouldCheckForRequiredKeyword);
if (jsonPropertyInfo == null)
{
// ignored invalid property
}
Debug.Assert(jsonPropertyInfo.Name != null);
- Debug.Assert(PropertyCache != null);
- CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers);
- propertyOrderSpecified |= jsonPropertyInfo.Order != 0;
+ typeInfo.AddProperty(jsonPropertyInfo, ref state);
}
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
- Justification = "The ctor is marked as RequiresUnreferencedCode")]
- [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
- Justification = "The ctor is marked RequiresDynamicCode.")]
- private JsonPropertyInfo? CreateProperty(
+ [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+ [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+ private static JsonPropertyInfo? CreatePropertyInfo(
+ JsonTypeInfo typeInfo,
Type typeToConvert,
MemberInfo memberInfo,
JsonSerializerOptions options,
{
JsonIgnoreCondition? ignoreCondition = memberInfo.GetCustomAttribute<JsonIgnoreAttribute>(inherit: false)?.Condition;
- if (IsInvalidForSerialization(typeToConvert))
+ if (JsonTypeInfo.IsInvalidForSerialization(typeToConvert))
{
if (ignoreCondition == JsonIgnoreCondition.Always)
return null;
return null;
}
- JsonPropertyInfo jsonPropertyInfo = CreatePropertyUsingReflection(typeToConvert);
+ JsonPropertyInfo jsonPropertyInfo = typeInfo.CreatePropertyUsingReflection(typeToConvert);
jsonPropertyInfo.InitializeUsingMemberReflection(memberInfo, customConverter, ignoreCondition, shouldCheckForRequiredKeyword);
return jsonPropertyInfo;
}
ignoredMember.IsVirtual;
}
- internal override JsonParameterInfoValues[] GetParameterInfoValues()
+ private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo)
{
- Debug.Assert(Converter.ConstructorInfo != null);
- ParameterInfo[] parameters = Converter.ConstructorInfo.GetParameters();
+ Debug.Assert(typeInfo.Converter.ConstructorInfo != null);
+ ParameterInfo[] parameters = typeInfo.Converter.ConstructorInfo.GetParameters();
int parameterCount = parameters.Length;
JsonParameterInfoValues[] jsonParameters = new JsonParameterInfoValues[parameterCount];
// Trimmed parameter names are reported as null in CoreCLR or "" in Mono.
if (string.IsNullOrEmpty(reflectionInfo.Name))
{
- Debug.Assert(Converter.ConstructorInfo.DeclaringType != null);
- ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(Converter.ConstructorInfo.DeclaringType);
+ Debug.Assert(typeInfo.Converter.ConstructorInfo.DeclaringType != null);
+ ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(typeInfo.Converter.ConstructorInfo.DeclaringType);
}
JsonParameterInfoValues jsonInfo = new()
jsonParameters[i] = jsonInfo;
}
- return jsonParameters;
+ typeInfo.ParameterInfoValues = jsonParameters;
}
}
}
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Text.Json.Reflection;
using System.Threading;
namespace System.Text.Json.Serialization.Metadata
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions options)
{
- JsonTypeInfo jsonTypeInfo;
JsonConverter converter = GetConverterForType(type, options);
-
- if (converter.TypeToConvert == type)
- {
- // For performance, avoid doing a reflection-based instantiation
- // if the converter type matches that of the declared type.
- jsonTypeInfo = converter.CreateReflectionJsonTypeInfo(options);
- }
- else
- {
- Type jsonTypeInfoType = typeof(ReflectionJsonTypeInfo<>).MakeGenericType(type);
- jsonTypeInfo = (JsonTypeInfo)jsonTypeInfoType.CreateInstanceNoWrapExceptions(
- parameterTypes: new Type[] { typeof(JsonConverter), typeof(JsonSerializerOptions) },
- parameters: new object[] { converter, options })!;
- }
-
- Debug.Assert(jsonTypeInfo.Type == type);
- return jsonTypeInfo;
+ return CreateTypeInfoCore(type, converter, options);
}
/// <summary>
/// <returns>Serialization metadata for the given type.</returns>
/// <remarks>This API is for use by the output of the System.Text.Json source generator and should not be called directly.</remarks>
public static JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(JsonSerializerOptions options, JsonCollectionInfoValues<TElement[]> collectionInfo)
- => new SourceGenJsonTypeInfo<TElement[]>(
+ => CreateCore(
options,
collectionInfo,
() => new ArrayConverter<TElement[], TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : List<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new ListOfTConverter<TCollection, TElement>());
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : Dictionary<TKey, TValue>
where TKey : notnull
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>());
ThrowHelper.ThrowArgumentNullException(nameof(createRangeFunc));
}
- return new SourceGenJsonTypeInfo<TCollection>(
+ return CreateCore(
options,
collectionInfo,
() => new ImmutableDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>(),
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IDictionary<TKey, TValue>
where TKey : notnull
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>());
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IReadOnlyDictionary<TKey, TValue>
where TKey : notnull
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IReadOnlyDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>());
ThrowHelper.ThrowArgumentNullException(nameof(createRangeFunc));
}
- return new SourceGenJsonTypeInfo<TCollection>(
+ return CreateCore(
options,
collectionInfo,
() => new ImmutableEnumerableOfTConverter<TCollection, TElement>(),
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IList
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IListConverter<TCollection>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IList<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IListOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : ISet<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new ISetOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : ICollection<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new ICollectionOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : Stack<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new StackOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : Queue<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new QueueOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : ConcurrentStack<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new ConcurrentStackOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : ConcurrentQueue<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new ConcurrentQueueOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IEnumerable<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IEnumerableOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IAsyncEnumerable<TElement>
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IAsyncEnumerableOfTConverter<TCollection, TElement>());
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IDictionary
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IDictionaryConverter<TCollection>());
ThrowHelper.ThrowArgumentNullException(nameof(addFunc));
}
- return new SourceGenJsonTypeInfo<TCollection>(
+ return CreateCore(
options,
collectionInfo,
() => new StackOrQueueConverter<TCollection>(),
JsonSerializerOptions options,
JsonCollectionInfoValues<TCollection> collectionInfo)
where TCollection : IEnumerable
- => new SourceGenJsonTypeInfo<TCollection>(
+ => CreateCore(
options,
collectionInfo,
() => new IEnumerableConverter<TCollection>());
--- /dev/null
+// 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.Reflection;
+using System.Text.Json.Serialization.Converters;
+
+namespace System.Text.Json.Serialization.Metadata
+{
+ public static partial class JsonMetadataServices
+ {
+ /// <summary>
+ /// Creates serialization metadata for a type using a simple converter.
+ /// </summary>
+ private static JsonTypeInfo<T> CreateCore<T>(JsonConverter converter, JsonSerializerOptions options)
+ {
+ JsonTypeInfo<T> typeInfo = new JsonTypeInfo<T>(converter, options);
+ typeInfo.PopulatePolymorphismMetadata();
+ typeInfo.MapInterfaceTypesToCallbacks();
+
+ // Plug in any converter configuration -- should be run last.
+ converter.ConfigureJsonTypeInfo(typeInfo, options);
+ return typeInfo;
+ }
+
+ /// <summary>
+ /// Creates serialization metadata for an object.
+ /// </summary>
+ private static JsonTypeInfo<T> CreateCore<T>(JsonSerializerOptions options, JsonObjectInfoValues<T> objectInfo)
+ {
+ JsonConverter<T> converter = GetConverter(objectInfo);
+ JsonTypeInfo<T> typeInfo = new JsonTypeInfo<T>(converter, options);
+ if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
+ {
+ typeInfo.CreateObjectWithArgs = objectInfo.ObjectWithParameterizedConstructorCreator;
+ PopulateParameterInfoValues(typeInfo, objectInfo.ConstructorParameterMetadataInitializer);
+ }
+ else
+ {
+ typeInfo.SetCreateObjectIfCompatible(objectInfo.ObjectCreator);
+ typeInfo.CreateObjectForExtensionDataProperty = ((JsonTypeInfo)typeInfo).CreateObject;
+ }
+
+ PopulateProperties(typeInfo, objectInfo.PropertyMetadataInitializer);
+ typeInfo.SerializeHandler = objectInfo.SerializeHandler;
+ typeInfo.NumberHandling = objectInfo.NumberHandling;
+ typeInfo.PopulatePolymorphismMetadata();
+ typeInfo.MapInterfaceTypesToCallbacks();
+
+ // Plug in any converter configuration -- should be run last.
+ converter.ConfigureJsonTypeInfo(typeInfo, options);
+ return typeInfo;
+ }
+
+ /// <summary>
+ /// Creates serialization metadata for a collection.
+ /// </summary>
+ private static JsonTypeInfo<T> CreateCore<T>(
+ JsonSerializerOptions options,
+ JsonCollectionInfoValues<T> collectionInfo,
+ Func<JsonConverter<T>> converterCreator,
+ object? createObjectWithArgs = null,
+ object? addFunc = null)
+ {
+ JsonConverter<T> converter = new JsonMetadataServicesConverter<T>(converterCreator());
+ JsonTypeInfo<T> typeInfo = new JsonTypeInfo<T>(converter, options);
+ if (collectionInfo is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(collectionInfo));
+ }
+
+ typeInfo.KeyTypeInfo = collectionInfo.KeyInfo;
+ typeInfo.ElementTypeInfo = collectionInfo.ElementInfo;
+ Debug.Assert(typeInfo.Kind != JsonTypeInfoKind.None);
+ typeInfo.NumberHandling = collectionInfo.NumberHandling;
+ typeInfo.SerializeHandler = collectionInfo.SerializeHandler;
+ typeInfo.CreateObjectWithArgs = createObjectWithArgs;
+ typeInfo.AddMethodDelegate = addFunc;
+ typeInfo.SetCreateObjectIfCompatible(collectionInfo.ObjectCreator);
+ typeInfo.PopulatePolymorphismMetadata();
+ typeInfo.MapInterfaceTypesToCallbacks();
+
+ // Plug in any converter configuration -- should be run last.
+ converter.ConfigureJsonTypeInfo(typeInfo, options);
+ return typeInfo;
+ }
+
+ private static JsonMetadataServicesConverter<T> GetConverter<T>(JsonObjectInfoValues<T> objectInfo)
+ {
+#pragma warning disable CS8714
+ // The type cannot be used as type parameter in the generic type or method.
+ // Nullability of type argument doesn't match 'notnull' constraint.
+ if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
+ {
+ return new JsonMetadataServicesConverter<T>(
+ () => new LargeObjectWithParameterizedConstructorConverter<T>(),
+ ConverterStrategy.Object);
+ }
+ else
+ {
+ return new JsonMetadataServicesConverter<T>(() => new ObjectDefaultConverter<T>(), ConverterStrategy.Object);
+ }
+#pragma warning restore CS8714
+ }
+
+ private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo, Func<JsonParameterInfoValues[]?>? paramFactory)
+ {
+ Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
+ Debug.Assert(!typeInfo.IsReadOnly);
+
+ if (paramFactory?.Invoke() is JsonParameterInfoValues[] array)
+ {
+ typeInfo.ParameterInfoValues = array;
+ }
+ else
+ {
+ typeInfo.MetadataSerializationNotSupported = true;
+ }
+ }
+
+ private static void PopulateProperties(JsonTypeInfo typeInfo, Func<JsonSerializerContext, JsonPropertyInfo[]?>? propInitFunc)
+ {
+ Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
+ Debug.Assert(!typeInfo.IsReadOnly);
+
+ JsonSerializerContext? context = typeInfo.Options.TypeInfoResolver as JsonSerializerContext;
+ if (propInitFunc?.Invoke(context!) is not JsonPropertyInfo[] properties)
+ {
+ if (typeInfo.Type == JsonTypeInfo.ObjectType)
+ {
+ return;
+ }
+
+ if (typeInfo.Converter.ElementType != null)
+ {
+ // Nullable<> or F# optional converter strategy is set to element strategy
+ return;
+ }
+
+ typeInfo.MetadataSerializationNotSupported = true;
+ return;
+ }
+
+ // TODO update the source generator so that all property
+ // hierarchy resolution is happening at compile time.
+ JsonTypeInfo.PropertyHierarchyResolutionState state = new();
+
+ foreach (JsonPropertyInfo jsonPropertyInfo in properties)
+ {
+ if (!jsonPropertyInfo.SrcGen_IsPublic)
+ {
+ if (jsonPropertyInfo.SrcGen_HasJsonInclude)
+ {
+ Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen");
+ ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType);
+ }
+
+ continue;
+ }
+
+ if (jsonPropertyInfo.MemberType == MemberTypes.Field && !jsonPropertyInfo.SrcGen_HasJsonInclude && !typeInfo.Options.IncludeFields)
+ {
+ continue;
+ }
+
+ typeInfo.AddProperty(jsonPropertyInfo, ref state);
+ }
+
+ // NB we don't need to sort source gen properties here since they were already sorted at compile time.
+ }
+ }
+}
ThrowHelper.ThrowArgumentNullException(nameof(objectInfo));
}
- return new SourceGenJsonTypeInfo<T>(options, objectInfo);
+ return CreateCore(options, objectInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(converter));
}
- JsonTypeInfo<T> info = new SourceGenJsonTypeInfo<T>(converter, options);
+ JsonTypeInfo<T> info = CreateCore<T>(converter, options);
return info;
}
}
internal void Configure()
{
- if (!IsForTypeInfo)
+ Debug.Assert(ParentTypeInfo != null);
+
+ if (IsIgnored)
{
- CacheNameAsUtf8BytesAndEscapedNameSection();
+ // Avoid configuring JsonIgnore.Always properties
+ // to avoid failing on potentially unsupported types.
+ CanSerialize = false;
+ CanDeserialize = false;
+ }
+ else
+ {
+ _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType, ensureConfigured: false);
+ DetermineEffectiveConverter(_jsonTypeInfo);
+ DetermineNumberHandlingForProperty();
+ DetermineSerializationCapabilities();
+ DetermineIgnoreCondition();
}
-
- _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType, ensureConfigured: false);
- DetermineEffectiveConverter(_jsonTypeInfo);
if (IsForTypeInfo)
{
}
else
{
- DetermineNumberHandlingForProperty();
- DetermineIgnoreCondition();
- DetermineSerializationCapabilities();
+ CacheNameAsUtf8BytesAndEscapedNameSection();
}
if (IsRequired)
private void DetermineSerializationCapabilities()
{
Debug.Assert(EffectiveConverter != null, "Must have calculated the effective converter.");
-
CanSerialize = HasGetter;
CanDeserialize = HasSetter;
internal Type DeclaringType { get; }
- [AllowNull]
internal JsonTypeInfo JsonTypeInfo
{
get
}
set
{
- // This could potentially be double initialized
- Debug.Assert(_jsonTypeInfo == null || _jsonTypeInfo == value);
_jsonTypeInfo = value;
}
}
- internal bool IsIgnored => _ignoreCondition == JsonIgnoreCondition.Always;
+ /// <summary>
+ /// Property was marked JsonIgnoreCondition.Always and also hasn't been configured by the user.
+ /// </summary>
+ internal bool IsIgnored => _ignoreCondition is JsonIgnoreCondition.Always && Get is null && Set is null;
/// <summary>
/// Reflects the value of <see cref="HasGetter"/> combined with any additional global ignore policies.
internal JsonPropertyDictionary<JsonParameterInfo>? ParameterCache { get; private set; }
// All of the serializable properties on a POCO (except the optional extension property) keyed on property name.
- internal JsonPropertyDictionary<JsonPropertyInfo>? PropertyCache { get; private protected set; }
+ internal JsonPropertyDictionary<JsonPropertyInfo>? PropertyCache { get; private set; }
// Fast cache of constructor parameters by first JSON ordering; may not contain all parameters. Accessed before ParameterCache.
// Use an array (instead of List<T>) for highest performance.
// Use an array (instead of List<T>) for highest performance.
private volatile PropertyRef[]? _propertyRefsSorted;
- internal Func<JsonSerializerContext, JsonPropertyInfo[]>? PropInitFunc;
-
- internal Func<JsonParameterInfoValues[]>? CtorParamInitFunc;
-
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
internal JsonPropertyInfo CreatePropertyUsingReflection(Type propertyType)
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text.Json.Reflection;
+using System.Text.Json.Serialization.Converters;
using System.Threading;
using System.Threading.Tasks;
internal delegate T ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>(TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3);
- private JsonPropertyInfoList? _properties;
-
/// <summary>
/// Indices of required properties.
/// </summary>
private Action<object>? _onDeserializing;
private Action<object>? _onDeserialized;
+ internal JsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
+ {
+ Type = type;
+ Options = options;
+ Converter = converter;
+ Kind = GetTypeInfoKind(type, converter);
+ PropertyInfoForTypeInfo = CreatePropertyInfoForTypeInfo();
+ ElementType = converter.ElementType;
+ KeyType = converter.KeyType;
+ }
+
/// <summary>
/// Gets or sets a parameterless factory to be used on deserialization.
/// </summary>
/// It is required that added <see cref="JsonPropertyInfo"/> entries are unique up to <see cref="JsonPropertyInfo.Name"/>,
/// however this will only be validated on serialization, once the metadata instance gets locked for further modification.
/// </remarks>
- public IList<JsonPropertyInfo> Properties
+ public IList<JsonPropertyInfo> Properties => _properties ??= new(this);
+ private JsonPropertyInfoList? _properties;
+
+ internal void SortProperties()
{
- get
- {
- if (_properties == null)
- {
- PopulatePropertyList();
- }
+ Debug.Assert(!IsConfigured);
+ Debug.Assert(_properties != null && _properties.Count > 0);
- return _properties;
- }
+ _properties.SortProperties();
+ PropertyCache?.List.StableSortByKey(static propInfo => propInfo.Value.Order);
}
/// <summary>
// Configure would normally have thrown why initializing properties for source gen but type had SerializeHandler
// so it is allowed to be used for fast-path serialization but it will throw if used for metadata-based serialization
- internal bool MetadataSerializationNotSupported { get; private protected set; }
+ internal bool MetadataSerializationNotSupported { get; set; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ValidateCanBeUsedForMetadataSerialization()
internal JsonUnmappedMemberHandling EffectiveUnmappedMemberHandling { get; private set; }
- internal JsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
- {
- Type = type;
- Options = options;
- Converter = converter;
- PropertyInfoForTypeInfo = CreatePropertyInfoForTypeInfo();
- ElementType = converter.ElementType;
-
- switch (converter.ConverterStrategy)
- {
- case ConverterStrategy.Dictionary:
- {
- KeyType = converter.KeyType;
- }
- break;
- case ConverterStrategy.Object:
- case ConverterStrategy.Enumerable:
- case ConverterStrategy.Value:
- break;
- case ConverterStrategy.None:
- {
- ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
- }
- break;
- default:
- Debug.Fail($"Unexpected class type: {converter.ConverterStrategy}");
- throw new InvalidOperationException();
- }
-
- Kind = GetTypeInfoKind(type, converter.ConverterStrategy);
- }
-
internal void VerifyMutable()
{
if (IsReadOnly)
Options.MakeReadOnly();
}
- PropertyInfoForTypeInfo.EnsureChildOf(this);
PropertyInfoForTypeInfo.EnsureConfigured();
CanUseSerializeHandler &= Options.CanUseFastPathSerializationLogic;
- JsonConverter converter = Converter;
Debug.Assert(PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy == Converter.ConverterStrategy,
$"ConverterStrategy from PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy ({PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy}) does not match converter's ({Converter.ConverterStrategy})");
if (Kind == JsonTypeInfoKind.Object)
{
- InitializePropertyCache();
+ ConfigureProperties();
- if (converter.ConstructorIsParameterized)
+ if (Converter.ConstructorIsParameterized)
{
- InitializeConstructorParameters(GetParameterInfoValues(), sourceGenMode: Options.TypeInfoResolver is JsonSerializerContext);
+ ConfigureConstructorParameters();
}
}
if (propCacheInitialized)
{
sb.AppendLine(" Properties: {");
- foreach (var property in PropertyCache!.List)
+ foreach (JsonPropertyInfo pi in PropertyCache!.Values)
{
- JsonPropertyInfo pi = property.Value;
- sb.AppendLine($" {property.Key}:");
+ sb.AppendLine($" {pi.Name}:");
sb.AppendLine($"{pi.GetDebugInfo(indent: 6)},");
}
}
#endif
- internal virtual void LateAddProperties() { }
-
/// <summary>
/// Creates a blank <see cref="JsonTypeInfo{T}"/> instance.
/// </summary>
}
JsonConverter converter = DefaultJsonTypeInfoResolver.GetConverterForType(typeof(T), options, resolveJsonConverterAttribute: false);
- return new CustomJsonTypeInfo<T>(converter, options);
+ return new JsonTypeInfo<T>(converter, options);
}
/// <summary>
ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(type), type, null, null);
}
- JsonTypeInfo jsonTypeInfo;
JsonConverter converter = DefaultJsonTypeInfoResolver.GetConverterForType(type, options, resolveJsonConverterAttribute: false);
+ return CreateJsonTypeInfo(type, converter, options);
+ }
+
+ [RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
+ [RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
+ internal static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
+ {
+ JsonTypeInfo jsonTypeInfo;
if (converter.TypeToConvert == type)
{
// For performance, avoid doing a reflection-based instantiation
// if the converter type matches that of the declared type.
- jsonTypeInfo = converter.CreateCustomJsonTypeInfo(options);
+ jsonTypeInfo = converter.CreateJsonTypeInfo(options);
}
else
{
- Type jsonTypeInfoType = typeof(CustomJsonTypeInfo<>).MakeGenericType(type);
+ Type jsonTypeInfoType = typeof(JsonTypeInfo<>).MakeGenericType(type);
jsonTypeInfo = (JsonTypeInfo)jsonTypeInfoType.CreateInstanceNoWrapExceptions(
parameterTypes: new Type[] { typeof(JsonConverter), typeof(JsonSerializerOptions) },
parameters: new object[] { converter, options })!;
return propertyInfo;
}
- [MemberNotNull(nameof(_properties))]
- private void PopulatePropertyList()
- {
- Debug.Assert(!Monitor.IsEntered(_configureLock), "should not be invoked from Configure");
-
- if (!_isConfigured)
- {
- // For mutable instances we need to synchronize access
- // with Configure() calls, otherwise we risk corrupting property state.
- lock (_configureLock)
- {
- if (!_isConfigured)
- {
- LateAddProperties();
- _properties = new(this);
- return;
- }
- }
- }
-
- _properties = new(this);
- }
-
- internal abstract JsonParameterInfoValues[] GetParameterInfoValues();
+ internal JsonParameterInfoValues[]? ParameterInfoValues { get; set; }
// Untyped, root-level serialization methods
internal abstract void SerializeAsObject(Utf8JsonWriter writer, object? rootValue, bool isInvokedByPolymorphicConverter = false);
internal abstract object? DeserializeAsObject(Stream utf8Json);
internal abstract IAsyncEnumerable<object?> DeserializeAsyncEnumerableAsObject(Stream utf8Json, CancellationToken cancellationToken);
- internal void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDictionary<JsonPropertyInfo> propertyCache, ref Dictionary<string, JsonPropertyInfo>? ignoredMembers)
+ /// <summary>
+ /// Used by the built-in resolvers to add property metadata applying conflict resolution.
+ /// </summary>
+ internal void AddProperty(JsonPropertyInfo jsonPropertyInfo, ref PropertyHierarchyResolutionState state)
{
Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName can be null in custom JsonPropertyInfo instances and should never be passed in this method");
string memberName = jsonPropertyInfo.MemberName;
- jsonPropertyInfo.EnsureChildOf(this);
+ JsonPropertyInfoList properties = _properties ??= new(this);
- if (jsonPropertyInfo.IsExtensionData)
+ if (state.AddedProperties.TryAdd(jsonPropertyInfo.Name, (jsonPropertyInfo, properties.Count)))
{
- if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
- {
- ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, jsonPropertyInfo);
- }
-
- if (ExtensionDataProperty != null)
- {
- ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
- }
-
- ExtensionDataProperty = jsonPropertyInfo;
- return;
+ properties.Add(jsonPropertyInfo);
+ state.IsPropertyOrderSpecified |= jsonPropertyInfo.Order != 0;
}
-
- // The JsonPropertyNameAttribute or naming policy resulted in a collision.
- if (!propertyCache.TryAdd(jsonPropertyInfo.Name, jsonPropertyInfo))
+ else
{
- JsonPropertyInfo other = propertyCache[jsonPropertyInfo.Name]!;
+ // The JsonPropertyNameAttribute or naming policy resulted in a collision.
+ (JsonPropertyInfo other, int index) = state.AddedProperties[jsonPropertyInfo.Name];
if (other.IsIgnored)
{
// Overwrite previously cached property since it has [JsonIgnore].
- propertyCache[jsonPropertyInfo.Name] = jsonPropertyInfo;
+ state.AddedProperties[jsonPropertyInfo.Name] = (jsonPropertyInfo, index);
+ properties[index] = jsonPropertyInfo;
+ state.IsPropertyOrderSpecified |= jsonPropertyInfo.Order != 0;
}
else
{
other.MemberName == memberName ||
// Was a property with the same CLR name ignored? That property hid the current property,
// thus, if it was ignored, the current property should be ignored too.
- ignoredMembers?.ContainsKey(memberName) == true;
+ state.IgnoredProperties?.ContainsKey(memberName) == true;
}
else
{
if (jsonPropertyInfo.IsIgnored)
{
- (ignoredMembers ??= new()).Add(memberName, jsonPropertyInfo);
+ (state.IgnoredProperties ??= new()).Add(memberName, jsonPropertyInfo);
}
}
+ internal ref struct PropertyHierarchyResolutionState
+ {
+ public PropertyHierarchyResolutionState() { }
+ public Dictionary<string, (JsonPropertyInfo, int index)> AddedProperties = new();
+ public Dictionary<string, JsonPropertyInfo>? IgnoredProperties;
+ public bool IsPropertyOrderSpecified;
+ }
+
private sealed class ParameterLookupKey
{
public ParameterLookupKey(string name, Type type)
public JsonPropertyInfo JsonPropertyInfo { get; }
}
- internal void InitializePropertyCache()
+ internal void ConfigureProperties()
{
Debug.Assert(Kind == JsonTypeInfoKind.Object);
+ Debug.Assert(PropertyCache is null);
+ Debug.Assert(ExtensionDataProperty is null);
+
+ IList<JsonPropertyInfo> properties = (IList<JsonPropertyInfo>?)_properties ?? Array.Empty<JsonPropertyInfo>();
+
+ JsonPropertyDictionary<JsonPropertyInfo> propertyCache = CreatePropertyCache(capacity: properties.Count);
+ int numberOfRequiredProperties = 0;
+ bool arePropertiesSorted = true;
+ int previousPropertyOrder = int.MinValue;
- if (_properties != null)
+ foreach (JsonPropertyInfo property in properties)
{
- // Properties have been exported to a metadata resolver,
- // invalidate the property cache and build from scratch
+ Debug.Assert(property.ParentTypeInfo == this);
- ExtensionDataProperty = null;
- if (PropertyCache is null)
+ if (property.IsExtensionData)
{
- PropertyCache = CreatePropertyCache(capacity: _properties.Count);
+ if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
+ {
+ ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, property);
+ }
+
+ if (ExtensionDataProperty != null)
+ {
+ ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
+ }
+
+ ExtensionDataProperty = property;
}
else
{
- PropertyCache.Clear();
- }
+ if (property.IsRequired)
+ {
+ property.RequiredPropertyIndex = numberOfRequiredProperties++;
+ }
- bool isOrderSpecified = false;
- foreach (JsonPropertyInfo property in _properties)
- {
- if (property.IsExtensionData)
+ if (arePropertiesSorted)
{
- if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
- {
- ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, property);
- }
-
- if (ExtensionDataProperty != null)
- {
- ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
- }
-
- ExtensionDataProperty = property;
- continue;
+ arePropertiesSorted = previousPropertyOrder <= property.Order;
+ previousPropertyOrder = property.Order;
}
- if (!PropertyCache.TryAddValue(property.Name, property))
+ if (!propertyCache.TryAddValue(property.Name, property))
{
ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, property.Name);
}
-
- isOrderSpecified |= property.Order != 0;
}
- if (isOrderSpecified)
- {
- PropertyCache.List.StableSortByKey(static prop => prop.Value.Order);
- }
- }
- else
- {
- // Resolver didn't modify any properties, create the property cache from scratch.
- LateAddProperties();
- PropertyCache ??= CreatePropertyCache(capacity: 0);
+ property.EnsureConfigured();
}
- if (ExtensionDataProperty != null)
- {
- ExtensionDataProperty.EnsureChildOf(this);
- ExtensionDataProperty.EnsureConfigured();
- }
+ NumberOfRequiredProperties = numberOfRequiredProperties;
+ PropertyCache = propertyCache;
- int numberOfRequiredProperties = 0;
- foreach (KeyValuePair<string, JsonPropertyInfo> jsonPropertyInfoKv in PropertyCache.List)
+ if (!arePropertiesSorted)
{
- JsonPropertyInfo jsonPropertyInfo = jsonPropertyInfoKv.Value;
-
- if (jsonPropertyInfo.IsRequired)
- {
- jsonPropertyInfo.RequiredPropertyIndex = numberOfRequiredProperties++;
- }
-
- jsonPropertyInfo.EnsureChildOf(this);
- jsonPropertyInfo.EnsureConfigured();
+ // Properties have been configured by the user and require sorting.
+ SortProperties();
}
- NumberOfRequiredProperties = numberOfRequiredProperties;
// Override global UnmappedMemberHandling configuration
// if type specifies an extension data property.
EffectiveUnmappedMemberHandling = UnmappedMemberHandling ??
: JsonUnmappedMemberHandling.Skip);
}
- internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonParameters, bool sourceGenMode = false)
+ internal void ConfigureConstructorParameters()
{
- Debug.Assert(ParameterCache is null);
Debug.Assert(Kind == JsonTypeInfoKind.Object);
+ Debug.Assert(Converter.ConstructorIsParameterized);
+ Debug.Assert(PropertyCache is not null);
+ Debug.Assert(ParameterCache is null);
+
+ JsonParameterInfoValues[] jsonParameters = ParameterInfoValues ?? Array.Empty<JsonParameterInfoValues>();
+ bool sourceGenMode = Options.TypeInfoResolver is JsonSerializerContext;
var parameterCache = new JsonPropertyDictionary<JsonParameterInfo>(Options.PropertyNameCaseInsensitive, jsonParameters.Length);
// record types or anonymous types are used.
// The property name key does not use [JsonPropertyName] or PropertyNamingPolicy since we only bind
// the parameter name to the object property name and do not use the JSON version of the name here.
- var nameLookup = new Dictionary<ParameterLookupKey, ParameterLookupValue>(PropertyCache!.Count);
+ var nameLookup = new Dictionary<ParameterLookupKey, ParameterLookupValue>(PropertyCache.Count);
foreach (KeyValuePair<string, JsonPropertyInfo> kvp in PropertyCache.List)
{
JsonParameterInfo jsonParameterInfo = CreateConstructorParameter(parameterInfo, jsonPropertyInfo, sourceGenMode, Options);
parameterCache.Add(jsonPropertyInfo.Name, jsonParameterInfo);
}
- // It is invalid for the extension data property to bind with a constructor argument.
+ // It is invalid for the extension data property to bind to a constructor argument.
else if (ExtensionDataProperty != null &&
StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, ExtensionDataProperty.Name))
{
return type == typeof(void) || type.IsPointer || type.IsByRef || IsByRefLike(type) || type.ContainsGenericParameters;
}
+ internal void PopulatePolymorphismMetadata()
+ {
+ Debug.Assert(!IsReadOnly);
+
+ JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type);
+ if (options != null)
+ {
+ options.DeclaringTypeInfo = this;
+ _polymorphismOptions = options;
+ }
+ }
+
+ internal void MapInterfaceTypesToCallbacks()
+ {
+ Debug.Assert(!IsReadOnly);
+
+ // Callbacks currently only supported in object kinds
+ // TODO: extend to collections/dictionaries
+ if (Kind == JsonTypeInfoKind.Object)
+ {
+ if (typeof(IJsonOnSerializing).IsAssignableFrom(Type))
+ {
+ OnSerializing = static obj => ((IJsonOnSerializing)obj).OnSerializing();
+ }
+
+ if (typeof(IJsonOnSerialized).IsAssignableFrom(Type))
+ {
+ OnSerialized = static obj => ((IJsonOnSerialized)obj).OnSerialized();
+ }
+
+ if (typeof(IJsonOnDeserializing).IsAssignableFrom(Type))
+ {
+ OnDeserializing = static obj => ((IJsonOnDeserializing)obj).OnDeserializing();
+ }
+
+ if (typeof(IJsonOnDeserialized).IsAssignableFrom(Type))
+ {
+ OnDeserialized = static obj => ((IJsonOnDeserialized)obj).OnDeserialized();
+ }
+ }
+ }
+
+ internal void SetCreateObjectIfCompatible(Delegate? createObject)
+ {
+ Debug.Assert(!IsReadOnly);
+
+ // Guard against the reflection resolver/source generator attempting to pass
+ // a CreateObject delegate to converters/metadata that do not support it.
+ if (Converter.SupportsCreateObjectDelegate && !Converter.ConstructorIsParameterized)
+ {
+ SetCreateObject(createObject);
+ }
+ }
+
private static bool IsByRefLike(Type type)
{
#if NETCOREAPP
return jsonParameterInfo;
}
- private static JsonTypeInfoKind GetTypeInfoKind(Type type, ConverterStrategy converterStrategy)
+ private static JsonTypeInfoKind GetTypeInfoKind(Type type, JsonConverter converter)
{
- // System.Object is polymorphic and will not respect Properties
- if (type == typeof(object))
+ if (type == typeof(object) && converter.CanBePolymorphic)
{
+ // System.Object is polymorphic and will not respect Properties
+ Debug.Assert(converter is ObjectConverter);
return JsonTypeInfoKind.None;
}
- return converterStrategy switch
+ switch (converter.ConverterStrategy)
{
- ConverterStrategy.Object => JsonTypeInfoKind.Object,
- ConverterStrategy.Enumerable => JsonTypeInfoKind.Enumerable,
- ConverterStrategy.Dictionary => JsonTypeInfoKind.Dictionary,
- _ => JsonTypeInfoKind.None
- };
+ case ConverterStrategy.Value: return JsonTypeInfoKind.None;
+ case ConverterStrategy.Object: return JsonTypeInfoKind.Object;
+ case ConverterStrategy.Enumerable: return JsonTypeInfoKind.Enumerable;
+ case ConverterStrategy.Dictionary: return JsonTypeInfoKind.Dictionary;
+ case ConverterStrategy.None:
+ Debug.Assert(converter is JsonConverterFactory);
+ ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
+ return default;
+ default:
+ Debug.Fail($"Unexpected class type: {converter.ConverterStrategy}");
+ throw new InvalidOperationException();
+ }
}
private sealed class JsonPropertyInfoList : ConfigurationList<JsonPropertyInfo>
private readonly JsonTypeInfo _jsonTypeInfo;
public JsonPropertyInfoList(JsonTypeInfo jsonTypeInfo)
- : base(jsonTypeInfo.PropertyCache?.Values)
{
- if (jsonTypeInfo.ExtensionDataProperty is not null)
- {
- _list.Add(jsonTypeInfo.ExtensionDataProperty);
- }
-
_jsonTypeInfo = jsonTypeInfo;
}
- protected override bool IsImmutable => _jsonTypeInfo.IsConfigured || _jsonTypeInfo.Kind != JsonTypeInfoKind.Object;
+ protected override bool IsImmutable => _jsonTypeInfo.IsReadOnly || _jsonTypeInfo.Kind != JsonTypeInfoKind.Object;
protected override void VerifyMutable()
{
_jsonTypeInfo.VerifyMutable();
{
item.EnsureChildOf(_jsonTypeInfo);
}
+
+ public void SortProperties()
+ => _list.StableSortByKey(static propInfo => propInfo.Order);
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
/// Provides JSON serialization-related metadata about a type.
/// </summary>
/// <typeparam name="T">The generic definition of the type.</typeparam>
- public abstract partial class JsonTypeInfo<T> : JsonTypeInfo
+ public sealed partial class JsonTypeInfo<T> : JsonTypeInfo
{
private Action<Utf8JsonWriter, T>? _serialize;
private Func<T>? _typedCreateObject;
+ internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
+ : base(typeof(T), converter, options)
+ {
+ EffectiveConverter = converter.CreateCastingConverter<T>();
+ }
+
/// <summary>
/// A Converter whose declared type always matches that of the current JsonTypeInfo.
/// It might be the same instance as JsonTypeInfo.Converter or it could be wrapped
_typedCreateObject = typedCreateObject;
}
- private protected void SetCreateObjectIfCompatible(Delegate? createObject)
- {
- Debug.Assert(!IsConfigured);
-
- // Guard against the reflection resolver/source generator attempting to pass
- // a CreateObject delegate to converters/metadata that do not support it.
- if (Converter.SupportsCreateObjectDelegate && !Converter.ConstructorIsParameterized)
- {
- SetCreateObject(createObject);
- }
- }
-
- internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
- : base(typeof(T), converter, options)
- {
- EffectiveConverter = converter.CreateCastingConverter<T>();
- }
-
/// <summary>
/// Serializes an instance of <typeparamref name="T"/> using
/// <see cref="JsonSourceGenerationOptionsAttribute"/> values specified at design time.
{
return _serialize;
}
- private protected set
+ internal set
{
- Debug.Assert(!IsConfigured, "We should not mutate configured JsonTypeInfo");
+ Debug.Assert(!IsReadOnly, "We should not mutate read-only JsonTypeInfo");
_serialize = value;
CanUseSerializeHandler = value != null;
}
{
return new JsonPropertyInfo<T>(
declaringType: typeof(T),
- declaringTypeInfo: null,
+ declaringTypeInfo: this,
Options)
{
JsonTypeInfo = this,
JsonTypeInfo = this
};
}
-
- private protected void PopulatePolymorphismMetadata()
- {
- JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type);
- if (options != null)
- {
- options.DeclaringTypeInfo = this;
- _polymorphismOptions = options;
- }
- }
-
- private protected void MapInterfaceTypesToCallbacks()
- {
- // Callbacks currently only supported in object kinds
- // TODO: extend to collections/dictionaries
- if (Kind == JsonTypeInfoKind.Object)
- {
- if (typeof(IJsonOnSerializing).IsAssignableFrom(typeof(T)))
- {
- OnSerializing = static obj => ((IJsonOnSerializing)obj).OnSerializing();
- }
-
- if (typeof(IJsonOnSerialized).IsAssignableFrom(typeof(T)))
- {
- OnSerialized = static obj => ((IJsonOnSerialized)obj).OnSerialized();
- }
-
- if (typeof(IJsonOnDeserializing).IsAssignableFrom(typeof(T)))
- {
- OnDeserializing = static obj => ((IJsonOnDeserializing)obj).OnDeserializing();
- }
-
- if (typeof(IJsonOnDeserialized).IsAssignableFrom(typeof(T)))
- {
- OnDeserialized = static obj => ((IJsonOnDeserialized)obj).OnDeserialized();
- }
- }
- }
}
}
+++ /dev/null
-// 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.Reflection;
-using System.Text.Json.Serialization.Converters;
-
-namespace System.Text.Json.Serialization.Metadata
-{
- /// <summary>
- /// Creates and initializes serialization metadata for a type.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- internal sealed class SourceGenJsonTypeInfo<T> : JsonTypeInfo<T>
- {
- /// <summary>
- /// Creates serialization metadata for a type using a simple converter.
- /// </summary>
- public SourceGenJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
- : base(converter, options)
- {
- PopulatePolymorphismMetadata();
- MapInterfaceTypesToCallbacks();
-
- // Plug in any converter configuration -- should be run last.
- converter.ConfigureJsonTypeInfo(this, options);
- }
-
- /// <summary>
- /// Creates serialization metadata for an object.
- /// </summary>
- public SourceGenJsonTypeInfo(JsonSerializerOptions options, JsonObjectInfoValues<T> objectInfo) : base(GetConverter(objectInfo), options)
- {
- if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
- {
- CreateObjectWithArgs = objectInfo.ObjectWithParameterizedConstructorCreator;
- CtorParamInitFunc = objectInfo.ConstructorParameterMetadataInitializer;
- }
- else
- {
- SetCreateObjectIfCompatible(objectInfo.ObjectCreator);
- CreateObjectForExtensionDataProperty = ((JsonTypeInfo)this).CreateObject;
- }
-
- PropInitFunc = objectInfo.PropertyMetadataInitializer;
- SerializeHandler = objectInfo.SerializeHandler;
- NumberHandling = objectInfo.NumberHandling;
- PopulatePolymorphismMetadata();
- MapInterfaceTypesToCallbacks();
-
- // Plug in any converter configuration -- should be run last.
- Converter.ConfigureJsonTypeInfo(this, options);
- }
-
- /// <summary>
- /// Creates serialization metadata for a collection.
- /// </summary>
- public SourceGenJsonTypeInfo(
- JsonSerializerOptions options,
- JsonCollectionInfoValues<T> collectionInfo,
- Func<JsonConverter<T>> converterCreator,
- object? createObjectWithArgs = null,
- object? addFunc = null)
- : base(new JsonMetadataServicesConverter<T>(converterCreator()), options)
- {
- if (collectionInfo is null)
- {
- ThrowHelper.ThrowArgumentNullException(nameof(collectionInfo));
- }
-
- KeyTypeInfo = collectionInfo.KeyInfo;
- ElementTypeInfo = collectionInfo.ElementInfo;
- Debug.Assert(Kind != JsonTypeInfoKind.None);
- NumberHandling = collectionInfo.NumberHandling;
- SerializeHandler = collectionInfo.SerializeHandler;
- CreateObjectWithArgs = createObjectWithArgs;
- AddMethodDelegate = addFunc;
- SetCreateObjectIfCompatible(collectionInfo.ObjectCreator);
- PopulatePolymorphismMetadata();
- MapInterfaceTypesToCallbacks();
-
- // Plug in any converter configuration -- should be run last.
- Converter.ConfigureJsonTypeInfo(this, options);
- }
-
- private static JsonMetadataServicesConverter<T> GetConverter(JsonObjectInfoValues<T> objectInfo)
- {
-#pragma warning disable CS8714
- // The type cannot be used as type parameter in the generic type or method.
- // Nullability of type argument doesn't match 'notnull' constraint.
- if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
- {
- return new JsonMetadataServicesConverter<T>(
- () => new LargeObjectWithParameterizedConstructorConverter<T>(),
- ConverterStrategy.Object);
- }
- else
- {
- return new JsonMetadataServicesConverter<T>(() => new ObjectDefaultConverter<T>(), ConverterStrategy.Object);
- }
-#pragma warning restore CS8714
- }
-
- internal override JsonParameterInfoValues[] GetParameterInfoValues()
- {
- JsonParameterInfoValues[] array;
- if (CtorParamInitFunc == null || (array = CtorParamInitFunc()) == null)
- {
- array = Array.Empty<JsonParameterInfoValues>();
- MetadataSerializationNotSupported = true;
- }
-
- return array;
- }
-
- internal override void LateAddProperties()
- {
- Debug.Assert(!IsConfigured);
- Debug.Assert(PropertyCache is null);
-
- if (Kind != JsonTypeInfoKind.Object)
- {
- return;
- }
-
- JsonSerializerContext? context = Options.TypeInfoResolver as JsonSerializerContext;
- JsonPropertyInfo[] array;
- if (PropInitFunc == null || (array = PropInitFunc(context!)) == null)
- {
- if (typeof(T) == typeof(object))
- {
- return;
- }
-
- if (Converter.ElementType != null)
- {
- // Nullable<> or F# optional converter's strategy is set to element's strategy
- return;
- }
-
- MetadataSerializationNotSupported = true;
- return;
- }
-
- Dictionary<string, JsonPropertyInfo>? ignoredMembers = null;
- JsonPropertyDictionary<JsonPropertyInfo> propertyCache = CreatePropertyCache(capacity: array.Length);
-
- for (int i = 0; i < array.Length; i++)
- {
- JsonPropertyInfo jsonPropertyInfo = array[i];
- bool hasJsonInclude = jsonPropertyInfo.SrcGen_HasJsonInclude;
-
- if (!jsonPropertyInfo.SrcGen_IsPublic)
- {
- if (hasJsonInclude)
- {
- Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen");
- ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType);
- }
-
- continue;
- }
-
- if (jsonPropertyInfo.MemberType == MemberTypes.Field && !hasJsonInclude && !Options.IncludeFields)
- {
- continue;
- }
-
- CacheMember(jsonPropertyInfo, propertyCache, ref ignoredMembers);
- }
-
- PropertyCache = propertyCache;
- }
- }
-}
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
// Soft cut-off length - once message becomes longer than that we won't be adding more elements
const int CutOffLength = 50;
- for (int propertyIdx = 0; propertyIdx < parent.PropertyCache.List.Count; propertyIdx++)
+ foreach (KeyValuePair<string, JsonPropertyInfo> kvp in parent.PropertyCache.List)
{
- JsonPropertyInfo property = parent.PropertyCache.List[propertyIdx].Value;
+ JsonPropertyInfo property = kvp.Value;
if (!property.IsRequired || requiredPropertiesSet[property.RequiredPropertyIndex])
{