-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Respect JsonSerializerOptions casing for property names in validation errors #62036
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?
Changes from 2 commits
d3d01af
03af06c
216406a
f92b43e
0900ced
2694d90
193c6a7
c1cfc9e
28b6aa1
e94aff2
9db960a
e9f9a2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
using System.ComponentModel.DataAnnotations; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text.Json; | ||
|
||
namespace Microsoft.AspNetCore.Http.Validation; | ||
|
||
|
@@ -60,41 +61,139 @@ public sealed class ValidateContext | |
/// </summary> | ||
public int CurrentDepth { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the JSON serializer options to use for property name formatting. | ||
/// When set, property names in validation errors will be formatted according to the | ||
/// PropertyNamingPolicy and JsonPropertyName attributes. | ||
/// </summary> | ||
public JsonSerializerOptions? SerializerOptions { get; set; } | ||
|
||
internal void AddValidationError(string key, string[] error) | ||
{ | ||
ValidationErrors ??= []; | ||
|
||
ValidationErrors[key] = error; | ||
var formattedKey = FormatKey(key); | ||
ValidationErrors[formattedKey] = error; | ||
} | ||
|
||
internal void AddOrExtendValidationErrors(string key, string[] errors) | ||
{ | ||
ValidationErrors ??= []; | ||
|
||
if (ValidationErrors.TryGetValue(key, out var existingErrors)) | ||
var formattedKey = FormatKey(key); | ||
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors)) | ||
{ | ||
var newErrors = new string[existingErrors.Length + errors.Length]; | ||
existingErrors.CopyTo(newErrors, 0); | ||
errors.CopyTo(newErrors, existingErrors.Length); | ||
ValidationErrors[key] = newErrors; | ||
ValidationErrors[formattedKey] = newErrors; | ||
} | ||
else | ||
{ | ||
ValidationErrors[key] = errors; | ||
ValidationErrors[formattedKey] = errors; | ||
} | ||
} | ||
|
||
internal void AddOrExtendValidationError(string key, string error) | ||
{ | ||
ValidationErrors ??= []; | ||
|
||
if (ValidationErrors.TryGetValue(key, out var existingErrors) && !existingErrors.Contains(error)) | ||
var formattedKey = FormatKey(key); | ||
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors) && !existingErrors.Contains(error)) | ||
{ | ||
ValidationErrors[key] = [.. existingErrors, error]; | ||
ValidationErrors[formattedKey] = [.. existingErrors, error]; | ||
} | ||
else | ||
{ | ||
ValidationErrors[key] = [error]; | ||
ValidationErrors[formattedKey] = [error]; | ||
} | ||
} | ||
|
||
private string FormatKey(string key) | ||
{ | ||
if (string.IsNullOrEmpty(key) || SerializerOptions?.PropertyNamingPolicy is null) | ||
{ | ||
return key; | ||
} | ||
|
||
// If the key contains a path (e.g., "Address.Street" or "Items[0].Name"), | ||
// apply the naming policy to each part of the path | ||
if (key.Contains('.') || key.Contains('[')) | ||
{ | ||
return FormatComplexKey(key); | ||
} | ||
|
||
// For JsonPropertyName attribute support, we'd need property info | ||
// but for basic usage, apply the naming policy directly | ||
return SerializerOptions.PropertyNamingPolicy.ConvertName(key); | ||
} | ||
|
||
private string FormatComplexKey(string key) | ||
{ | ||
// Use a more direct approach for complex keys with dots and array indices | ||
var result = new System.Text.StringBuilder(); | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
int lastIndex = 0; | ||
int i = 0; | ||
bool inBracket = false; | ||
|
||
while (i < key.Length) | ||
{ | ||
char c = key[i]; | ||
|
||
if (c == '[') | ||
{ | ||
// Format the segment before the bracket | ||
if (i > lastIndex) | ||
{ | ||
string segment = key.Substring(lastIndex, i - lastIndex); | ||
string formattedSegment = SerializerOptions!.PropertyNamingPolicy!.ConvertName(segment); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For sanities sake, instead of using null-suppression here, we should check if PropertyNamingPolicy is not null and fall back to to This null suppression is technically safe because you've already validated that it is not null at the top of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've replaced null-suppression with proper null checking in the FormatComplexKey method in commit c1cfc9e. Now it first stores PropertyNamingPolicy in a local variable and then checks if it's not null before using it. |
||
result.Append(formattedSegment); | ||
} | ||
|
||
// Start collecting the bracket part | ||
inBracket = true; | ||
result.Append(c); | ||
lastIndex = i + 1; | ||
} | ||
else if (c == ']') | ||
{ | ||
// Add the content inside the bracket as-is | ||
if (i > lastIndex) | ||
{ | ||
string segment = key.Substring(lastIndex, i - lastIndex); | ||
result.Append(segment); | ||
} | ||
result.Append(c); | ||
inBracket = false; | ||
lastIndex = i + 1; | ||
} | ||
else if (c == '.' && !inBracket) | ||
{ | ||
// Format the segment before the dot | ||
if (i > lastIndex) | ||
{ | ||
string segment = key.Substring(lastIndex, i - lastIndex); | ||
string formattedSegment = SerializerOptions!.PropertyNamingPolicy!.ConvertName(segment); | ||
result.Append(formattedSegment); | ||
} | ||
result.Append(c); | ||
lastIndex = i + 1; | ||
} | ||
|
||
i++; | ||
} | ||
|
||
// Format the last segment if there is one | ||
if (lastIndex < key.Length) | ||
{ | ||
string segment = key.Substring(lastIndex); | ||
if (!inBracket) | ||
{ | ||
segment = SerializerOptions!.PropertyNamingPolicy!.ConvertName(segment); | ||
} | ||
result.Append(segment); | ||
} | ||
|
||
return result.ToString(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.