Skip to content

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

captainsafia
Copy link
Member

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 for JsonPropertyName 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:

    • Added a private method GetJsonPropertyName to determine effective property names during JSON serialization, considering JsonPropertyNameAttribute and naming policies.
    • Updated validation logic to use JSON naming policies and JsonPropertyName attributes for formatting property names and paths. [1] [2]
  • Performance Optimization:

    • Cached the presence of DisplayAttribute in the constructor of ValidatablePropertyInfo to avoid repeated reflection calls.
    • Introduced the HasDisplayAttribute method to determine if a property or its corresponding record parameter has a DisplayAttribute.

Dependency and Context Updates:

  • New Dependencies:
    • Added references to Microsoft.AspNetCore.Http.Extensions and System.Text.Json for JSON serialization support. [1] [2] [3]
    • Exposed JsonSerializerOptions in ValidateContext to retrieve JSON naming policies from the DI container.

Test Case Updates:

  • Validation Tests:
    • Updated test cases to verify the correct application of JSON naming policies and JsonPropertyName attributes in validation error messages. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

@github-actions github-actions bot added the area-infrastructure Includes: MSBuild projects/targets, build scripts, CI, Installers and shared framework label Jun 13, 2025
Copy link
Contributor

Hey @dotnet/aspnet-build, looks like this PR is something you want to take a look at.

@captainsafia captainsafia requested a review from Copilot June 13, 2025 05:31
Copy link
Contributor

@Copilot Copilot AI left a 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);
Copy link
Preview

Copilot AI Jun 13, 2025

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.

Suggested change
var property = DeclaringType.GetProperty(Name);
var property = DeclaringType.GetProperty(Name, BindingFlags.Public | BindingFlags.Instance);

Copilot uses AI. Check for mistakes.

Copy link
Member

@BrennanConroy BrennanConroy left a 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
Copy link
Member

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...)

Copy link
Member Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and primary ctors?

Copy link
Member Author

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;

Copy link
Member

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.

Copy link
Member Author

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...

@captainsafia captainsafia force-pushed the safia/valid-jsonoptions branch from ee27af8 to f29313d Compare June 13, 2025 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-infrastructure Includes: MSBuild projects/targets, build scripts, CI, Installers and shared framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants