-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configure non-nullable types as required #2036
Comments
Temporary solution: serviceCollection.AddSwaggerGen(builder =>
{
builder.SupportNonNullableReferenceTypes();
builder.SchemaFilter<RequiredNotNullableSchemaFilter>();
...
}); class RequiredNotNullableSchemaFilter : ISchemaFilter {
public void Apply(OpenApiSchema schema, SchemaFilterContext context) {
if (schema.Properties == null) {
return;
}
var notNullableProperties = schema
.Properties
.Where(x => !x.Value.Nullable && !schema.Required.Contains(x.Key))
.ToList();
foreach (var property in notNullableProperties)
{
schema.Required.Add(property.Key);
}
}
} UPD: This solution is not completely correct, see example: public class User
{
public Guid Id { get; set; }
public Guid? OrganizationId { get; set; }
public string FirstName { get; set; } = default!;
public string? MiddleName { get; set; }
public string LastName { get; set; } = default!;
public UserEmail Email { get; set; } = default!;
public UserPhone? Phone { get; set; }
}
public class UserEmail
{
public string Address { get; set; } = default!;
}
public class UserPhone
{
public string Number { get; set; } = default!;
} {
"User": {
"required": [
"email",
"firstName",
"id",
"lastName",
"phone" // Incorrect
],
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string",
"format": "uuid",
"nullable": true
},
"firstName": {
"type": "string"
},
"middleName": {
"type": "string",
"nullable": true
},
"lastName": {
"type": "string"
},
"email": {
"$ref": "#/components/schemas/UserEmail"
},
"phone": {
"$ref": "#/components/schemas/UserPhone"
}
},
"additionalProperties": false
} |
@flibustier7seas you mention that your solution isn't completely correct, and I see you mention the phone field being marked as required, even though it's not. Do you know what in particular causes this case, or if there's any workarounds? |
@JosNun I'm using the following workaround: class RequiredNotNullableSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema.Properties == null)
{
return;
}
FixNullableProperties(schema, context);
var notNullableProperties = schema
.Properties
.Where(x => !x.Value.Nullable && !schema.Required.Contains(x.Key))
.ToList();
foreach (var property in notNullableProperties)
{
schema.Required.Add(property.Key);
}
}
/// <summary>
/// Option "SupportNonNullableReferenceTypes" not working with complex types ({ "type": "object" }),
/// so they always have "Nullable = false",
/// see method "SchemaGenerator.GenerateSchemaForMember"
/// </summary>
private static void FixNullableProperties(OpenApiSchema schema, SchemaFilterContext context)
{
foreach (var property in schema.Properties)
{
if (property.Value.Reference != null)
{
var field = context.Type
.GetMembers(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(x =>
string.Equals(x.Name, property.Key, StringComparison.InvariantCultureIgnoreCase));
if (field != null)
{
var fieldType = field switch
{
FieldInfo fieldInfo => fieldInfo.FieldType,
PropertyInfo propertyInfo => propertyInfo.PropertyType,
_ => throw new NotSupportedException(),
};
property.Value.Nullable = fieldType.IsValueType
? Nullable.GetUnderlyingType(fieldType) != null
: !field.IsNonNullableReferenceType();
}
}
}
}
} Result: {
"User": {
"required": ["email", "firstName", "id", "lastName"],
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"organizationId": {
"type": "string",
"format": "uuid",
"nullable": true
},
"firstName": {
"type": "string"
},
"middleName": {
"type": "string",
"nullable": true
},
"lastName": {
"type": "string"
},
"email": {
"$ref": "#/components/schemas/UserEmail"
},
"phone": {
"$ref": "#/components/schemas/UserPhone"
}
},
"additionalProperties": false
}
} |
small modification on @flibustier7seas 's answer var notNullableProperties = schema
.Properties
.Where(x => !x.Value.Nullable && x.Value.Default == default && !schema.Required.Contains(x.Key))
.ToList(); |
If you use the |
I would love to see this work out of the box |
+1 For this feature |
Thx @flibustier7seas, that works for me! |
This won't work for |
With C# 11.0/.NET 7.0, the But required properties also affects the behavior of System.Text.Json 7.x deserialization and thereby aspnetcore model validation. The default behavior for System.Text.Json deserialization / aspnetcore model validation in .NET 7.0 is as follows.
Since nullablility and required/not are different things where one does not necessarily imply the other, I think SwasBuckle should use the C# property nullability for swagger property If the C# property is annotated with References: |
... And if C# 11's required modifier is specified it should also set required regardless of if it's a string or not. |
One more case to consider: avoiding inferred required attribute when default value is defined.
It’s non-nullable, yet it shoudn’t be required as it has a default value. See how ASP validation is working as a result of this issue |
Keep in mind that the required keyword rules everything now. |
Absolutely not. When you use
There’re a lot of use-cases for non-nullable but also not required properties. For example, some options with default values. You don’t want them to be nullable in your code (as they will never be with default value), yet you don’t want to force consumers to supply them, hence the default value:
↑ // non-nullable, yet not required. This is exactly how ASP checker works tldr; required non-nullable means you must provide the value; non-nullable with default value means, it’s ok to ommit as some sensible value will be used. |
We’re in agreement. No required means same as before. Required being there means always must be filled in. |
Yet some PRs and this sentence ↓ seem to ignore the default logic.
It obviosly CAN be ommited from json IF default value is set (this way it DOES not imply null). |
You're right, for this bullet there was a unintentional |
@josundt About your updated comment:
Is this really true for System.Text.Json deserialization / aspnetcore model validation? This doesn't make any sense to me. If it is implicitly null when it is omitted, then why can it be omitted if it can't be null? |
@cremor But I also see that it sometimes may be useful to make I agree that the combination |
@josundtit it will be “bad design decision” only if the default values are surprising to the user. Suppose you have a You have 3 options:
So as long as default value is clear, it’s completely reasonable to support this. Imagine if you were forced to explicitly supply every property of the class every time you were to create a new object in C#. |
i am no longer able to work on it. |
If anyone else would like to pick up the above fix and take it forward that would be welcome. |
In my project we do not distinguish between
required
andnullable: false
. If a type is non-nullable, it is also required. This is regardless if it is a value type or reference type.It would be great if one could optionally configure Swashbuckle to set all non-nullable properties to required. E.g. like:
setupAction.MarkNonNullableTypesAsRequired();
This way, I could remove all my[Required]
annotations on the non-nullable C# properties.This is a subsequent request following #1686. As originally posted by @felschr in #1686 (comment) , treating non-nullable as required is the default for nullable contexts in .NET validation.
The text was updated successfully, but these errors were encountered: