diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs index 574c305f32..e376e00b5b 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs @@ -136,8 +136,8 @@ private static object GetNullableAttribute(this MemberInfo memberInfo) private static bool GetNullableFallbackValue(this MemberInfo memberInfo) { var declaringTypes = memberInfo.DeclaringType.IsNested - ? new Type[] { memberInfo.DeclaringType, memberInfo.DeclaringType.DeclaringType } - : new Type[] { memberInfo.DeclaringType }; + ? GetDeclaringTypeChain(memberInfo) + : new List(1) { memberInfo.DeclaringType }; foreach (var declaringType in declaringTypes) { @@ -162,5 +162,19 @@ private static bool GetNullableFallbackValue(this MemberInfo memberInfo) return false; } + + private static List GetDeclaringTypeChain(MemberInfo memberInfo) + { + var chain = new List(); + var currentType = memberInfo.DeclaringType; + + while (currentType != null) + { + chain.Add(currentType); + currentType = currentType.DeclaringType; + } + + return chain; + } } } diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SchemaGenerator/JsonSerializerSchemaGeneratorTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SchemaGenerator/JsonSerializerSchemaGeneratorTests.cs index 88e20d03f9..7db3a66387 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SchemaGenerator/JsonSerializerSchemaGeneratorTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SchemaGenerator/JsonSerializerSchemaGeneratorTests.cs @@ -981,6 +981,28 @@ public void GenerateSchema_SupportsOption_SuppressImplicitRequiredAttributeForNo Assert.Equal(!suppress, propertyIsRequired); } + [Theory] + [InlineData(typeof(TypeWithNullableContextAnnotated), nameof(TypeWithNullableContextAnnotated.SubTypeWithNestedSubType.Nested), nameof(TypeWithNullableContextAnnotated.SubTypeWithNestedSubType.Nested.NullableString), true)] + [InlineData(typeof(TypeWithNullableContextAnnotated), nameof(TypeWithNullableContextAnnotated.SubTypeWithNestedSubType.Nested), nameof(TypeWithNullableContextAnnotated.SubTypeWithNestedSubType.Nested.NonNullableString), false)] + [InlineData(typeof(TypeWithNullableContextNotAnnotated), nameof(TypeWithNullableContextNotAnnotated.SubTypeWithNestedSubType.Nested), nameof(TypeWithNullableContextAnnotated.SubTypeWithNestedSubType.Nested.NullableString), true)] + [InlineData(typeof(TypeWithNullableContextNotAnnotated), nameof(TypeWithNullableContextNotAnnotated.SubTypeWithNestedSubType.Nested), nameof(TypeWithNullableContextAnnotated.SubTypeWithNestedSubType.Nested.NonNullableString), false)] + public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes_NestedWithinNested( + Type declaringType, + string subType, + string propertyName, + bool expectedNullable) + { + var subject = Subject( + configureGenerator: c => c.SupportNonNullableReferenceTypes = true + ); + var schemaRepository = new SchemaRepository(); + + subject.GenerateSchema(declaringType, schemaRepository); + + var propertySchema = schemaRepository.Schemas[subType].Properties[propertyName]; + Assert.Equal(expectedNullable, propertySchema.Nullable); + } + [Theory] [InlineData(typeof(TypeWithNullableContextAnnotated))] [InlineData(typeof(TypeWithNullableContextNotAnnotated))] diff --git a/test/Swashbuckle.AspNetCore.TestSupport/Fixtures/TypeWithNullableContext.cs b/test/Swashbuckle.AspNetCore.TestSupport/Fixtures/TypeWithNullableContext.cs index 81cf7dcb5c..8d83a01e1f 100644 --- a/test/Swashbuckle.AspNetCore.TestSupport/Fixtures/TypeWithNullableContext.cs +++ b/test/Swashbuckle.AspNetCore.TestSupport/Fixtures/TypeWithNullableContext.cs @@ -48,6 +48,7 @@ public class TypeWithNullableContextNotAnnotated public List? NullableList { get; set; } public List NonNullableList { get; set; } = default!; + public SubTypeWithNestedSubType NestedWithNested { get; set; } = default!; public Dictionary? NullableDictionaryInNonNullableContent { get; set; } public Dictionary NonNullableDictionaryInNonNullableContent { get; set; } = default!; @@ -88,6 +89,17 @@ public class SubTypeWithOneNonNullableContent { public string NonNullableString { get; set; } = default!; } + + public class SubTypeWithNestedSubType + { + public Nested NestedProperty { get; set; } = default!; + + public class Nested + { + public string? NullableString { get; set; } + public string NonNullableString { get; set; } = default!; + } + } } /// @@ -128,6 +140,7 @@ public class TypeWithNullableContextAnnotated public List? NullableList { get; set; } public List NonNullableList { get; set; } = default!; + public SubTypeWithNestedSubType NestedWithNested { get; set; } = default!; public Dictionary? NullableDictionaryInNonNullableContent { get; set; } public Dictionary NonNullableDictionaryInNonNullableContent { get; set; } = default!; @@ -168,6 +181,17 @@ public class SubTypeWithOneNonNullableContent { public string NonNullableString { get; set; } = default!; } + + public class SubTypeWithNestedSubType + { + public Nested NestedProperty { get; set; } = default!; + + public class Nested + { + public string? NullableString { get; set; } + public string NonNullableString { get; set; } = default!; + } + } } #nullable restore }