-
-
Notifications
You must be signed in to change notification settings - Fork 535
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
Recent JsonIgnoreCondition change breaks NSwag C# clients #1564
Comments
I agree with @ovska. The changes are causing some harm and the worst part is that i can not opt-out of it easily. I have also stumbled upon it here: RicoSuter/NSwag#4012 (comment) |
Can you popose a new PR? Is it really necessary to add a new option? Isnt there just a right behavior? |
I think the most flexible behavior is to leave the attribute out altogether. |
Same problem here |
This is complicated... I'll try to make it make sense... Our projects use Swashbuckle to generate openapi specs, which we use nswag to generate c# and typescript clients. This is an issue in certain validation scenarios in combination with nullable semantics. For example I often set value types in request models as nullable, ie a nullable enum property. This allows me to distinguish between a missing request value and a default value for a value type during request validation (using FluentValidation for example). But openapi with schema $ref's doesn't handle this well unless the enum itself is decorated with a swagger attribute to set In any case, when that is not done, then nswag generates the enum as non-nullable. Unless the nswag option to "generate optional schema properties as nullable" is enabled, then you get a non-nullable enum with the I don't see any clean way around this at the moment. I do not want to set the "generate optional..." flag, because that litters my c# clients with nullable properties where they are not intended (for example in response models, where defining especially value types as "required" is not desired). So I agree, I think we need an option to disable writing |
To solve my issue I added the enum to the required props(POCO is generated with Never option). My case is like the @ErikPilsits-RJW said, I dont use public class SwaggerRequiredPropertyFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var props = context.Type.GetProperties().Where(p => p.GetCustomAttribute<SwaggerRequiredPropertyAttribute>() is not null).ToList();
if (props.Count is 0)
return;
var explicitRequired = new HashSet<string>();
foreach (var item in schema.Properties)
{
if (props.Any(x => string.Compare(item.Key, x.Name, StringComparison.InvariantCultureIgnoreCase) is 0))
{
explicitRequired.Add(item.Key);
}
}
schema.Required.UnionWith(explicitRequired);
}
} |
In my case, the enum in the request is only conditionally required, it depends on other properties which are validated via FluentValidation. So the [Required] attribute is not a solution for me. |
@epilsits I dont use public class AddWeatherForecastRequest
{
public DateTime? Date { get; set; }
public string? Summary { get; set; }
[SwaggerRequiredProperty]
public MyEnum EnumValue { get; set; }
public int Age { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SwaggerRequiredPropertyAttribute : Attribute
{
} And the schema filter: public class SwaggerRequiredPropertyFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var props = context.Type.GetProperties().Where(p => p.GetCustomAttribute<SwaggerRequiredPropertyAttribute>() is not null).ToList();
if (props.Count is 0)
return;
var explicitRequired = new HashSet<string>();
foreach (var item in schema.Properties)
{
if (props.Any(x => string.Compare(item.Key, x.Name, StringComparison.InvariantCultureIgnoreCase) is 0))
{
explicitRequired.Add(item.Key);
}
}
schema.Required.UnionWith(explicitRequired);
}
} The generated OpenApi spec:
Then the DTO generated by NSwag: [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.18.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
public partial class AddWeatherForecastRequest
{
[System.Text.Json.Serialization.JsonPropertyName("date")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
public System.DateTimeOffset? Date { get; set; } = default!;
[System.Text.Json.Serialization.JsonPropertyName("summary")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
public string? Summary { get; set; } = default!;
[System.Text.Json.Serialization.JsonPropertyName("enumValue")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
public MyEnum EnumValue { get; set; } = default!;
[System.Text.Json.Serialization.JsonPropertyName("age")]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
public int Age { get; set; } = default!;
public string ToJson()
{
var options = new System.Text.Json.JsonSerializerOptions();
return System.Text.Json.JsonSerializer.Serialize(this, options);
}
public static AddWeatherForecastRequest FromJson(string data)
{
var options = new System.Text.Json.JsonSerializerOptions();
return System.Text.Json.JsonSerializer.Deserialize<AddWeatherForecastRequest>(data, options);
}
} This is not the best solution, but the only one I could think. |
I use Swashbuckle to generate my specs, so I guess I don't see the difference here between using For my usage, my point was I don't want these properties marked as required in the spec, so none of these three are options. |
@ErikPilsits-RJW I tried the That was the reason I created a custom filter, it only needs to decorate the property. |
A workaround for now is to modify the NJsonSchema liquid template {%- if property.HasJsonIgnoreCondition and property.JsonIgnoreCondition contains "JsonIgnoreCondition.Never" %}
[System.Text.Json.Serialization.JsonIgnore(Condition = {{ property.JsonIgnoreCondition }})]
{%- endif -%} |
Any news on this? Will there be ever a proper fix or opt-out? Currently i am stuck to a specific NSwag version before these changes. |
Doing package upgrade and just ran into this breaking change. Like others, went looking for a configuration flag to disable this JsonIgnore attribute being added to everything. Found this issue instead. Downgraded back to NSwag version 13.15.10. Really appreciate RicoSuter's and other contributors work on this project, but would suggest making the JsonIgnore attribute configurable, as this change breaks a ton of projects :) |
Same here, this has been a problem for over a year so we're having to start looking for an alternative to using NSwag since it looks like this isn't going to be fixed in the near future. (Found this issue via RicoSuter/NSwag#4012) |
I really encourage you to try my workaround. Getting custom templates set up is not hard at all, and gives you flexibility to change behavior to your requirements. There's a bit of a lift when a new version of nswag is released, if the release updates the internal templates, as you'll have to merge those changes into your own template. But in practice this has not been hard for me and does not happen with every release. |
@ErikPilsits-RJW Thanks, that's great. I originally dismissed that idea because I assumed it was more effort than it was worth, but you encouraged me to take a proper look at custom templates and it seems to work perfectly. For the benefit of others, here's what I did (I'm using NSwag from the command line via the dotnet tool, so this may or not apply to other people's setup):
What still confuses me is why this change was made in the first place 😕 |
We already had some liquid templates in place but i didnt know that i could also replace the NJsonSchema through the liquid templates as well. Thanks! |
Hi there, I'm the original author of the JsonIgnoreCondition code for System.Text.Json and I thought I should share a few thoughts. Generally if a property is not required by the JSON schema, it must be generated as a nullable type, as null is (conventionally) the only way to express omission in C#. If WhenWritingNull is causing problems then the problem is with the property type. If you have a property which is both nullable and not required in the schema, then the best you can do is to hope that there is no semantic difference between sending null and omission. WhenWritingDefault makes no sense at all, as obviously it needs to be possible to send the 0 value. |
Our team had the same issue with the NSwag autogenerated client and the model in it. We had similar issues as has been described on this page: Non-nullable Enum with value |
Thank you very much @mooski for you workaround, you saved my day! Pls consider fixing this, this is a very sneaky and annoying bug. |
@RicoSuter It would be great if you can focus on this issue - I think the community needs after one year a fix and not only a workaround. Please make a featureflag or rollback this feature or fix this WhenWritingDefault behaviour or whatever. I had a look at the code but for me there are too many unknowns so I can't do it. @jcracknell As you wrote you introduced this JsonIgnore feature in issue #1513. You are the knowledge owner of this stuff - please bring it to a good end - now this feature is in a broken state. Sorry but thats the truth. |
So if we revert the PRs |
I think yes. I also found only these two PRs about this feature. |
Was it intended to completely remove the attribute? Even the |
Is it actually needed to have this "Never"? Before the PR which introduced this attribute at all it seemed to be fine. |
Hi, our team is depedent upon What follows are simplified examples of json transferred over the wire to the backend. Case 1: set year to 2023 in backend{
"Year": 2023
} Case 2: set year to null in backend{
"Year": null
} Case 3: do not touch year at allAs {
} Our team are using |
@espenrl, You should be able to configure a default ignore condition by implementing UpdateJsonSerializerSettings in your client.
|
That's great. Thanks for the tip! |
Caused by #1522
Example from string serialized enums, here is the source enum:
Relevant part of OpenApi json:
Generated enum in C# client:
And finally the property w/ the enum in a generated class:
When
Value1
is used, the JSON property is never serialized, which causes the Scope enum to have an undefined value of0
at the C# controller side.The attribute can also have unintended consoquences when generating clients against runtimes that have different semantics from C#. For example, it's impossible to get the serializer to write a zero
int
orDateTime.MinValue
property.IMO the explicit ignore condition should never be used for value types, and I think it's debatable if it's required at all; when serializing you can opt to use
JsonSerializerOptions.DefaultIgnoreCondition
.The text was updated successfully, but these errors were encountered: