diff --git a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs index 53dcd2cfe71a27..87a0d2b95c96b4 100644 --- a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs @@ -324,5 +324,111 @@ public static bool TryGetDeserializationConstructor( return defaultValue; } + + /// + /// Returns the type hierarchy for the given type, starting from the current type up to the base type(s) in the hierarchy. + /// Interface hierarchies with multiple inheritance will return results using topological sorting. + /// + public static Type[] GetSortedTypeHierarchy( +#if !BUILDING_SOURCE_GENERATOR + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + this Type type) + { + if (!type.IsInterface) + { + // Non-interface hierarchies are linear, just walk up to the earliest ancestor. + + var results = new List(); + for (Type? current = type; current != null; current = current.BaseType) + { + results.Add(current); + } + + return results.ToArray(); + } + else + { + // Interface hierarchies support multiple inheritance, + // query the entire list and sort them topologically. + Type[] interfaces = type.GetInterfaces(); + { + // include the current type into the list of interfaces + Type[] newArray = new Type[interfaces.Length + 1]; + newArray[0] = type; + interfaces.CopyTo(newArray, 1); + interfaces = newArray; + } + + TopologicalSort(interfaces, static (t1, t2) => t1.IsAssignableFrom(t2)); + return interfaces; + } + } + + private static void TopologicalSort(T[] inputs, Func isLessThan) + where T : notnull + { + // Standard implementation of in-place topological sorting using Kahn's algorithm. + + if (inputs.Length < 2) + { + return; + } + + var graph = new Dictionary>(); + var next = new Queue(); + + // Step 1: construct the dependency graph. + for (int i = 0; i < inputs.Length; i++) + { + T current = inputs[i]; + HashSet? dependencies = null; + + for (int j = 0; j < inputs.Length; j++) + { + if (i != j && isLessThan(current, inputs[j])) + { + (dependencies ??= new()).Add(inputs[j]); + } + } + + if (dependencies is null) + { + next.Enqueue(current); + } + else + { + graph.Add(current, dependencies); + } + } + + Debug.Assert(next.Count > 0, "Input graph must be a DAG."); + int index = 0; + + // Step 2: Walk the dependency graph starting with nodes that have no dependencies. + do + { + T nextTopLevelDependency = next.Dequeue(); + + foreach (KeyValuePair> kvp in graph) + { + HashSet dependencies = kvp.Value; + if (dependencies.Count > 0) + { + dependencies.Remove(nextTopLevelDependency); + + if (dependencies.Count == 0) + { + next.Enqueue(kvp.Key); + } + } + } + + inputs[index++] = nextTopLevelDependency; + } + while (next.Count > 0); + + Debug.Assert(index == inputs.Length); + } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index cbb6e5b0ad0473..8d1f090093595d 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -1026,7 +1026,8 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener bool propertyOrderSpecified = false; - for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) + // Walk the type hierarchy starting from the current type up to the base type(s) + foreach (Type currentType in type.GetSortedTypeHierarchy()) { PropertyGenerationSpec spec; @@ -1260,6 +1261,7 @@ private PropertyGenerationSpec GetPropertyGenerationSpec( IsExtensionData = isExtensionData, TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode), DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(), + DeclaringType = memberInfo.DeclaringType, ConverterInstantiationLogic = converterInstantiationLogic, HasFactoryConverter = hasFactoryConverter }; diff --git a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs index 5417f25b6c649a..c885f4219c9452 100644 --- a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs @@ -110,6 +110,8 @@ internal sealed class PropertyGenerationSpec /// public string DeclaringTypeRef { get; init; } + public Type DeclaringType { get; init; } + /// /// Source code to instantiate design-time specified custom converter. /// diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index 770e45d395b235..08032264b725f8 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -166,6 +166,7 @@ public bool TryFilterSerializableProps( [NotNullWhen(true)] out Dictionary? serializableProperties, out bool castingRequiredForProps) { + castingRequiredForProps = false; serializableProperties = new Dictionary(); Dictionary? ignoredMembers = null; @@ -198,6 +199,10 @@ public bool TryFilterSerializableProps( continue; } + // Using properties from an interface hierarchy -- require explicit casting when + // getting properties in the fast path to account for possible diamond ambiguities. + castingRequiredForProps |= Type.IsInterface && propGenSpec.DeclaringType != Type; + string memberName = propGenSpec.ClrName!; // The JsonPropertyNameAttribute or naming policy resulted in a collision. @@ -210,32 +215,52 @@ public bool TryFilterSerializableProps( // Overwrite previously cached property since it has [JsonIgnore]. serializableProperties[propGenSpec.RuntimePropertyName] = propGenSpec; } - else if ( - // Does the current property have `JsonIgnoreAttribute`? - propGenSpec.DefaultIgnoreCondition != JsonIgnoreCondition.Always && - // Is the current property hidden by the previously cached property - // (with `new` keyword, or by overriding)? - other.ClrName != memberName && - // Was a property with the same CLR name was ignored? That property hid the current property, - // thus, if it was ignored, the current property should be ignored too. - ignoredMembers?.ContainsKey(memberName) != true) + else { - // We throw if we have two public properties that have the same JSON property name, and neither have been ignored. - serializableProperties = null; - castingRequiredForProps = false; - return false; + bool ignoreCurrentProperty; + + if (!Type.IsInterface) + { + ignoreCurrentProperty = + // Does the current property have `JsonIgnoreAttribute`? + propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always || + // Is the current property hidden by the previously cached property + // (with `new` keyword, or by overriding)? + 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; + } + else + { + // Unlike classes, interface hierarchies reject all naming conflicts for non-ignored properties. + // Conflicts like this are possible in two cases: + // 1. Diamond ambiguity in property names, or + // 2. Linear interface hierarchies that use properties with DIMs. + // + // Diamond ambiguities are not supported. Assuming there is demand, we might consider + // adding support for DIMs in the future, however that would require adding more APIs + // for the case of source gen. + + ignoreCurrentProperty = propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always; + } + + if (!ignoreCurrentProperty) + { + // We have a conflict, emit a stub method that throws. + goto ReturnFalse; + } } - // Ignore the current property. } if (propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always) { - (ignoredMembers ??= new Dictionary()).Add(memberName, propGenSpec); + (ignoredMembers ??= new()).Add(memberName, propGenSpec); } } Debug.Assert(PropertyGenSpecList.Count >= serializableProperties.Count); - castingRequiredForProps = PropertyGenSpecList.Count > serializableProperties.Count; + castingRequiredForProps |= PropertyGenSpecList.Count > serializableProperties.Count; return true; ReturnFalse: diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index e0937877f6c044..48a32b2a4a2dce 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -791,25 +791,46 @@ internal void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDiction // Overwrite previously cached property since it has [JsonIgnore]. propertyCache[jsonPropertyInfo.Name] = jsonPropertyInfo; } - else if ( - // Does the current property have `JsonIgnoreAttribute`? - !jsonPropertyInfo.IsIgnored && - // Is the current property hidden by the previously cached property - // (with `new` keyword, or by overriding)? - other.MemberName != memberName && - // Was a property with the same CLR name was ignored? That property hid the current property, - // thus, if it was ignored, the current property should be ignored too. - ignoredMembers?.ContainsKey(memberName) != true) + else { - // We throw if we have two public properties that have the same JSON property name, and neither have been ignored. - ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo.Name); + bool ignoreCurrentProperty; + + if (!Type.IsInterface) + { + ignoreCurrentProperty = + // Does the current property have `JsonIgnoreAttribute`? + jsonPropertyInfo.IsIgnored || + // Is the current property hidden by the previously cached property + // (with `new` keyword, or by overriding)? + 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; + } + else + { + // Unlike classes, interface hierarchies reject all naming conflicts for non-ignored properties. + // Conflicts like this are possible in two cases: + // 1. Diamond ambiguity in property names, or + // 2. Linear interface hierarchies that use properties with DIMs. + // + // Diamond ambiguities are not supported. Assuming there is demand, we might consider + // adding support for DIMs in the future, however that would require adding more APIs + // for the case of source gen. + + ignoreCurrentProperty = jsonPropertyInfo.IsIgnored; + } + + if (!ignoreCurrentProperty) + { + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo.Name); + } } - // Ignore the current property. } if (jsonPropertyInfo.IsIgnored) { - (ignoredMembers ??= new Dictionary()).Add(memberName, jsonPropertyInfo); + (ignoredMembers ??= new()).Add(memberName, jsonPropertyInfo); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs index 9e2fb80b3bd96d..fee942254b8015 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs @@ -32,7 +32,7 @@ internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions o converter.ConfigureJsonTypeInfoUsingReflection(this, options); } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:RequiresUnreferencedCode", + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern", Justification = "The ctor is marked RequiresUnreferencedCode.")] internal override void LateAddProperties() { @@ -44,104 +44,129 @@ internal override void LateAddProperties() return; } - const BindingFlags BindingFlags = - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic | - BindingFlags.DeclaredOnly; - - Dictionary? ignoredMembers = null; - bool propertyOrderSpecified = false; // 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); - // Walk through the inheritance hierarchy, starting from the most derived type upward. - for (Type? currentType = Type; currentType != null; currentType = currentType.BaseType) + bool propertyOrderSpecified = false; + Dictionary? ignoredMembers = null; + + // Walk the type hierarchy starting from the current type up to the base type(s) + foreach (Type currentType in Type.GetSortedTypeHierarchy()) { - PropertyInfo[] properties = currentType.GetProperties(BindingFlags); + if (currentType == ObjectType) + { + // Don't process any members for typeof(object) + break; + } + + AddMembersDeclaredBySuperType( + currentType, + shouldCheckMembersForRequiredMemberAttribute, + ref propertyOrderSpecified, + ref ignoredMembers); + } + + Debug.Assert(PropertyCache != null); + + if (propertyOrderSpecified) + { + PropertyCache.List.StableSortByKey(static p => p.Value.Order); + } + } + + [UnconditionalSuppressMessage("Trimming", "IL2070:UnrecognizedReflectionPattern", + Justification = "The ctor is marked RequiresUnreferencedCode.")] + private void AddMembersDeclaredBySuperType( + Type currentType, + bool shouldCheckMembersForRequiredMemberAttribute, + ref bool propertyOrderSpecified, + ref Dictionary? ignoredMembers) + { + Debug.Assert(!IsConfigured); + Debug.Assert(currentType.IsAssignableFrom(Type)); + + const BindingFlags BindingFlags = + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly; + + 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); + // 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) + 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)) { - string propertyName = propertyInfo.Name; + continue; + } - // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. - if (propertyInfo.GetIndexParameters().Length > 0 || - PropertyIsOverriddenAndIgnored(propertyName, propertyInfo.PropertyType, propertyInfo.IsVirtual(), ignoredMembers)) + // For now we only support public properties (i.e. setter and/or getter is public). + if (propertyInfo.GetMethod?.IsPublic == true || + propertyInfo.SetMethod?.IsPublic == true) + { + CacheMember( + typeToConvert: propertyInfo.PropertyType, + memberInfo: propertyInfo, + ref propertyOrderSpecified, + ref ignoredMembers, + shouldCheckMembersForRequiredMemberAttribute); + } + else + { + if (propertyInfo.GetCustomAttribute(inherit: false) != null) { - continue; + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyName, currentType); } - // For now we only support public properties (i.e. setter and/or getter is public). - if (propertyInfo.GetMethod?.IsPublic == true || - propertyInfo.SetMethod?.IsPublic == true) + // Non-public properties should not be included for (de)serialization. + } + } + + foreach (FieldInfo fieldInfo in currentType.GetFields(BindingFlags)) + { + string fieldName = fieldInfo.Name; + + if (PropertyIsOverriddenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers)) + { + continue; + } + + bool hasJsonInclude = fieldInfo.GetCustomAttribute(inherit: false) != null; + + if (fieldInfo.IsPublic) + { + if (hasJsonInclude || Options.IncludeFields) { CacheMember( - typeToConvert: propertyInfo.PropertyType, - memberInfo: propertyInfo, + typeToConvert: fieldInfo.FieldType, + memberInfo: fieldInfo, ref propertyOrderSpecified, ref ignoredMembers, shouldCheckMembersForRequiredMemberAttribute); } - else - { - if (propertyInfo.GetCustomAttribute(inherit: false) != null) - { - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyName, currentType); - } - - // Non-public properties should not be included for (de)serialization. - } } - - foreach (FieldInfo fieldInfo in currentType.GetFields(BindingFlags)) + else { - string fieldName = fieldInfo.Name; - - if (PropertyIsOverriddenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers)) + if (hasJsonInclude) { - continue; + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldName, currentType); } - bool hasJsonInclude = fieldInfo.GetCustomAttribute(inherit: false) != null; - - if (fieldInfo.IsPublic) - { - if (hasJsonInclude || Options.IncludeFields) - { - CacheMember( - typeToConvert: fieldInfo.FieldType, - memberInfo: fieldInfo, - ref propertyOrderSpecified, - ref ignoredMembers, - shouldCheckMembersForRequiredMemberAttribute); - } - } - else - { - if (hasJsonInclude) - { - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldName, currentType); - } - - // Non-public fields should not be included for (de)serialization. - } + // Non-public fields should not be included for (de)serialization. } } - - Debug.Assert(PropertyCache != null); - - if (propertyOrderSpecified) - { - PropertyCache.List.StableSortByKey(static p => p.Value.Order); - } } private void CacheMember( diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs index bf8be277756723..e0e4e3ab2dd067 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs @@ -2850,5 +2850,161 @@ public class ClassWithCallbacks public Action Action { get; set; } = (val) => Console.WriteLine(); } + + [Fact] + public async Task SimpleInterfaceHierarchy_Serialization_ShouldIncludeBaseInterfaceProperties() + { + var value = new ISimpleInterfaceHierarchy.Implementation + { + BaseProperty = 0, + DerivedProperty = 1, + DerivedProperty2 = 2, + }; + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"DerivedProperty":1,"BaseProperty":0}""", json); + + json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"DerivedProperty2":2,"DerivedProperty":1,"BaseProperty":0}""", json); + } + + public interface ISimpleInterfaceHierarchy + { + public int BaseProperty { get; set; } + + public interface IDerivedInterface1 : ISimpleInterfaceHierarchy + { + public int DerivedProperty { get; set; } + } + + public interface IDerivedInterface2 : IDerivedInterface1 + { + public int DerivedProperty2 { get; set; } + } + + public class Implementation : IDerivedInterface2 + { + public int BaseProperty { get; set; } + public int DerivedProperty { get; set; } + public int DerivedProperty2 { get; set; } + } + } + + [Fact] + public async Task DiamondInterfaceHierarchy_Serialization_ShouldIncludeBaseInterfaceProperties() + { + var value = new IDiamondInterfaceHierarchy.Implementation + { + BaseProperty = 0, + DerivedProperty = 1, + DerivedProperty2 = 2, + DerivedProperty3 = 3, + }; + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"DerivedProperty":1,"BaseProperty":0}""", json); + + json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"DerivedProperty2":2,"BaseProperty":0}""", json); + + json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"DerivedProperty3":3,"DerivedProperty2":2,"DerivedProperty":1,"BaseProperty":0}""", json); + } + + public interface IDiamondInterfaceHierarchy + { + public int BaseProperty { get; set; } + + public interface IDerivedInterface1 : IDiamondInterfaceHierarchy + { + public int DerivedProperty { get; set; } + } + + public interface IDerivedInterface2 : IDiamondInterfaceHierarchy + { + public int DerivedProperty2 { get; set; } + } + + public interface IJoinInterface : IDerivedInterface1, IDerivedInterface2 + { + public int DerivedProperty3 { get; set; } + } + + public class Implementation : IJoinInterface + { + public int BaseProperty { get; set; } + public int DerivedProperty { get; set; } + public int DerivedProperty2 { get; set; } + public int DerivedProperty3 { get; set; } + } + } + + [Fact] + public async Task DiamondInterfaceHierarchyWithNamingConflict_ThrowsJsonException() + { + var value = new IDiamondInterfaceHierarchyWithNamingConflict.Implementation + { + DerivedProperty = 1, + }; + + await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(value)); + } + + public interface IDiamondInterfaceHierarchyWithNamingConflict + { + public interface IDerivedInterface1 : IDiamondInterfaceHierarchyWithNamingConflict + { + public int DerivedProperty { get; set; } + } + + public interface IDerivedInterface2 : IDiamondInterfaceHierarchyWithNamingConflict + { + public int DerivedProperty { get; set; } + } + + public interface IJoinInterface : IDerivedInterface1, IDerivedInterface2 + { + } + + public class Implementation : IJoinInterface + { + public int DerivedProperty { get; set; } + } + } + + [Fact] + public async Task DiamondInterfaceHierarchyWithNamingConflict_UsingJsonPropertyName_WorksAsExpected() + { + var value = new IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute.Implementation + { + DerivedProperty = 1, + }; + + string json = await Serializer.SerializeWrapper(value); + JsonTestHelper.AssertJsonEqual("""{"DerivedProperty":1,"DerivedProperty2":1}""", json); + } + + public interface IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute + { + public interface IDerivedInterface1 : IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute + { + public int DerivedProperty { get; set; } + } + + public interface IDerivedInterface2 : IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute + { + [JsonPropertyName("DerivedProperty2")] + public int DerivedProperty { get; set; } + } + + public interface IJoinInterface : IDerivedInterface1, IDerivedInterface2 + { + } + + public class Implementation : IJoinInterface + { + public int DerivedProperty { get; set; } + } + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs index 624854c1bca3ff..f43228d76b796b 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs @@ -279,6 +279,13 @@ public override async Task HonorJsonPropertyName_PrivateSetter() [JsonSerializable(typeof(TypeWith_IgnoredPropWith_BadConverter))] [JsonSerializable(typeof(ClassWithIgnoredCallbacks))] [JsonSerializable(typeof(ClassWithCallbacks))] + [JsonSerializable(typeof(ISimpleInterfaceHierarchy.IDerivedInterface1))] + [JsonSerializable(typeof(ISimpleInterfaceHierarchy.IDerivedInterface2))] + [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IDerivedInterface1), TypeInfoPropertyName = "IDiamondInterfaceHierarchyIDerivedInterface1")] + [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IDerivedInterface2), TypeInfoPropertyName = "IDiamondInterfaceHierarchyIDerivedInterface2")] + [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IJoinInterface))] + [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflict.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictIJoinInterface")] + [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictUsingAttributeIJoinInterface")] internal sealed partial class PropertyVisibilityTestsContext_Metadata : JsonSerializerContext { } @@ -514,6 +521,13 @@ public static void PublicContexAndJsonSerializerOptions() [JsonSerializable(typeof(TypeWith_IgnoredPropWith_BadConverter))] [JsonSerializable(typeof(ClassWithIgnoredCallbacks))] [JsonSerializable(typeof(ClassWithCallbacks))] + [JsonSerializable(typeof(ISimpleInterfaceHierarchy.IDerivedInterface1))] + [JsonSerializable(typeof(ISimpleInterfaceHierarchy.IDerivedInterface2))] + [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IDerivedInterface1), TypeInfoPropertyName = "IDiamondInterfaceHierarchyIDerivedInterface1")] + [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IDerivedInterface2), TypeInfoPropertyName = "IDiamondInterfaceHierarchyIDerivedInterface2")] + [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IJoinInterface))] + [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflict.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictIJoinInterface")] + [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictUsingAttributeIJoinInterface")] internal sealed partial class PropertyVisibilityTestsContext_Default : JsonSerializerContext { }