-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Respect JsonSerializerOptions in validation errors #62341
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request enhances JSON serialization and validation by applying JSON naming policies and attributes consistently, while also improving performance through caching and updating corresponding tests. Key changes include updating expected validation error keys to match JSON naming policies, caching of DisplayAttribute checks in property validation, and exposing JsonSerializerOptions via dependency injection.
Reviewed Changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
src/Validation/test/Microsoft.Extensions.Validation.Tests/Microsoft.Extensions.Validation.Tests.csproj | Added dependency references for JSON serialization support. |
src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/*.cs | Updated expected error message keys in tests to lowercase according to the new naming policy. |
src/Validation/src/ValidateContext.cs | Exposed JsonSerializerOptions from DI and used new array literal initialization. |
src/Validation/src/ValidatableTypeInfo.cs | Uses JSON naming policy to format member names in validation errors. |
src/Validation/src/ValidatablePropertyInfo.cs | Integrated caching for DisplayAttribute and applied JSON naming policy consistently. |
src/Validation/src/Microsoft.Extensions.Validation.csproj | Updated dependency references. |
Comments suppressed due to low confidence (1)
src/Validation/src/ValidateContext.cs:71
- [nitpick] Ensure that the empty array literal '[]' properly initializes ValidationErrors as intended; if the target type is not directly inferrable, consider using 'new SomeType[0]' for clarity.
ValidationErrors ??= [];
@@ -28,12 +32,16 @@ protected ValidatablePropertyInfo( | |||
PropertyType = propertyType; | |||
Name = name; | |||
DisplayName = displayName; | |||
|
|||
// Cache the HasDisplayAttribute result to avoid repeated reflection calls | |||
var property = DeclaringType.GetProperty(Name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Consider specifying explicit BindingFlags (e.g. BindingFlags.Public | BindingFlags.Instance) with GetProperty to ensure the lookup behavior is unambiguous.
var property = DeclaringType.GetProperty(Name); | |
var property = DeclaringType.GetProperty(Name, BindingFlags.Public | BindingFlags.Instance); |
Copilot uses AI. Check for mistakes.
b5217e2
to
ee27af8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a test with a class with primary ctors and json attributes?
@@ -28,12 +32,16 @@ protected ValidatablePropertyInfo( | |||
PropertyType = propertyType; | |||
Name = name; | |||
DisplayName = displayName; | |||
|
|||
// Cache the HasDisplayAttribute result to avoid repeated reflection calls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Comment about why we don't use the name from the attribute
And maybe we should set DisplayName
to the attributes value if the provided value happens to be null? (I know the API definition says non-null but...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And maybe we should set DisplayName to the attributes value if the provided value happens to be null? (I know the API definition says non-null but...)
What would this help with?
} | ||
|
||
// Look for a constructor parameter matching the property name (case-insensitive) | ||
// to account for the record scenario |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and primary ctors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite since there isn't the same case-based 1:1 mapping between property names and parameter names when using primary constructors. It's a variation of #61526.
@@ -60,6 +64,8 @@ public sealed class ValidateContext | |||
/// </summary> | |||
public int CurrentDepth { get; set; } | |||
|
|||
internal JsonSerializerOptions? SerializerOptions => ValidationContext.GetService(typeof(IOptions<JsonOptions>)) is IOptions<JsonOptions> options ? options.Value.SerializerOptions : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal JsonSerializerOptions? SerializerOptions => ValidationContext.GetService(typeof(IOptions<JsonOptions>)) is IOptions<JsonOptions> options ? options.Value.SerializerOptions : null; | |
internal JsonSerializerOptions? SerializerOptions => ValidationContext.GetService<IOptions<JsonOptions>>()>?.Value.SerializerOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this works since you're embedding a local version of JsonOptions
into the package.
You should make a web app outside of the repo and test that this resolves the JsonOptions
from ConfigureHttpJsonOptions
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was testing with this last night and trying to figure out how to get it to work in a console app. It turns out that using private reflection might be the only way to do this.
I force pushed the change since I hadn't realized you started reviewing it...
ee27af8
to
f29313d
Compare
/// Attempts to resolve the <see cref="JsonSerializerOptions"/> used for serialization | ||
/// using reflection to access JsonOptions from the ASP.NET Core shared framework. | ||
/// </summary> | ||
private JsonSerializerOptions? ResolveSerializerOptions() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How AOT friendly is this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's generally safe. The main issue would be the resolution of the IOptions<JsonOptions>
type but since that's always configured in the DI container for ASP.NET Core apps, I don't expect it to be trimmed away. The other issue would beJsonOptions.SerializerOptions
but I don't think that's likely to be trimmed away given how it's referenced in the framework.
I am long overdue for adding a native AoT test project to this package... :/
…a/valid-jsonoptions
@javiercn @oroztocil Would appreciate your thoughts here. The goal here is to support using the STJ naming policy for both the property keys and the member names in error messages. The This does introduce an STJ dependency into the extensions package but I think it is crafted such that there should be any impact for the WASM scenario. This maybe have an impact for the Blazor Server scenario since I believe JsonOptions might be loaded there. In a previous iteration of this API, we explicitly set a |
@captainsafia I'm not sure I understand all the details about this but let me put it in my own words to see if I understood correctly. This adds support for DisplayName (or that existed, and I wasn't aware of) and S.T.J conventions.
We currently rely on the mapping being 1:1 between key/path and object path, so any transform there is something we would need to account for and potentially "undo". For us there is no "value" in having such transforms, on the contrary, it gets in our way because we have to do extra work to reverse the mapping (camel casing might work, but snake_casing or kebab-casing won't). One alternative way to go about this might be to abstract the naming policy away into something like an (IErrorNamingPolicy) that can then be added to the ValidationOptions. You could then source generate the STJ version of that into the user app along with the glue you need to wire things up when you replace The other bit would be that I would not mind this being "on by default" but I would like a way to "turn off" this behavior from within the framework at the time we are going to run the validation. For example, maybe ValidateContext has a flag Ideally, we shouldn't require the users to do additional work for the framework to work in the way it is expected to do so. If we want to be all or nothing, instead of I'm happy to discuss this further. |
This pull request introduces enhancements to JSON serialization and validation in the
Microsoft.Extensions.Validation
library. Key changes include improved handling of JSON naming policies, support forJsonPropertyName
attributes, and caching of reflection results to optimize performance. Additionally, test cases have been updated to reflect these changes.Enhancements to JSON Serialization and Validation:
Support for JSON Naming Policies and Attributes:
GetJsonPropertyName
to determine effective property names during JSON serialization, consideringJsonPropertyNameAttribute
and naming policies.JsonPropertyName
attributes for formatting property names and paths. [1] [2]Performance Optimization:
DisplayAttribute
in the constructor ofValidatablePropertyInfo
to avoid repeated reflection calls.HasDisplayAttribute
method to determine if a property or its corresponding record parameter has aDisplayAttribute
.Dependency and Context Updates:
Microsoft.AspNetCore.Http.Extensions
andSystem.Text.Json
for JSON serialization support. [1] [2] [3]JsonSerializerOptions
inValidateContext
to retrieve JSON naming policies from the DI container.Test Case Updates:
JsonPropertyName
attributes in validation error messages. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]