Skip to content

Commit 03af06c

Browse files
Copilotcaptainsafia
andcommitted
Add support for JsonSerializerOptions property naming policy in validation errors
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
1 parent d3d01af commit 03af06c

File tree

5 files changed

+592
-11
lines changed

5 files changed

+592
-11
lines changed

src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentDepth.get -> int
2323
Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentDepth.set -> void
2424
Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentValidationPath.get -> string!
2525
Microsoft.AspNetCore.Http.Validation.ValidateContext.CurrentValidationPath.set -> void
26+
Microsoft.AspNetCore.Http.Validation.ValidateContext.SerializerOptions.get -> System.Text.Json.JsonSerializerOptions?
27+
Microsoft.AspNetCore.Http.Validation.ValidateContext.SerializerOptions.set -> void
2628
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidateContext() -> void
2729
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationContext.get -> System.ComponentModel.DataAnnotations.ValidationContext!
2830
Microsoft.AspNetCore.Http.Validation.ValidateContext.ValidationContext.set -> void

src/Http/Http.Abstractions/src/Validation/ValidateContext.cs

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.ComponentModel.DataAnnotations;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Text.Json;
67

78
namespace Microsoft.AspNetCore.Http.Validation;
89

@@ -60,41 +61,139 @@ public sealed class ValidateContext
6061
/// </summary>
6162
public int CurrentDepth { get; set; }
6263

64+
/// <summary>
65+
/// Gets or sets the JSON serializer options to use for property name formatting.
66+
/// When set, property names in validation errors will be formatted according to the
67+
/// PropertyNamingPolicy and JsonPropertyName attributes.
68+
/// </summary>
69+
public JsonSerializerOptions? SerializerOptions { get; set; }
70+
6371
internal void AddValidationError(string key, string[] error)
6472
{
6573
ValidationErrors ??= [];
6674

67-
ValidationErrors[key] = error;
75+
var formattedKey = FormatKey(key);
76+
ValidationErrors[formattedKey] = error;
6877
}
6978

7079
internal void AddOrExtendValidationErrors(string key, string[] errors)
7180
{
7281
ValidationErrors ??= [];
7382

74-
if (ValidationErrors.TryGetValue(key, out var existingErrors))
83+
var formattedKey = FormatKey(key);
84+
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors))
7585
{
7686
var newErrors = new string[existingErrors.Length + errors.Length];
7787
existingErrors.CopyTo(newErrors, 0);
7888
errors.CopyTo(newErrors, existingErrors.Length);
79-
ValidationErrors[key] = newErrors;
89+
ValidationErrors[formattedKey] = newErrors;
8090
}
8191
else
8292
{
83-
ValidationErrors[key] = errors;
93+
ValidationErrors[formattedKey] = errors;
8494
}
8595
}
8696

8797
internal void AddOrExtendValidationError(string key, string error)
8898
{
8999
ValidationErrors ??= [];
90100

91-
if (ValidationErrors.TryGetValue(key, out var existingErrors) && !existingErrors.Contains(error))
101+
var formattedKey = FormatKey(key);
102+
if (ValidationErrors.TryGetValue(formattedKey, out var existingErrors) && !existingErrors.Contains(error))
92103
{
93-
ValidationErrors[key] = [.. existingErrors, error];
104+
ValidationErrors[formattedKey] = [.. existingErrors, error];
94105
}
95106
else
96107
{
97-
ValidationErrors[key] = [error];
108+
ValidationErrors[formattedKey] = [error];
109+
}
110+
}
111+
112+
private string FormatKey(string key)
113+
{
114+
if (string.IsNullOrEmpty(key) || SerializerOptions?.PropertyNamingPolicy is null)
115+
{
116+
return key;
117+
}
118+
119+
// If the key contains a path (e.g., "Address.Street" or "Items[0].Name"),
120+
// apply the naming policy to each part of the path
121+
if (key.Contains('.') || key.Contains('['))
122+
{
123+
return FormatComplexKey(key);
98124
}
125+
126+
// For JsonPropertyName attribute support, we'd need property info
127+
// but for basic usage, apply the naming policy directly
128+
return SerializerOptions.PropertyNamingPolicy.ConvertName(key);
129+
}
130+
131+
private string FormatComplexKey(string key)
132+
{
133+
// Use a more direct approach for complex keys with dots and array indices
134+
var result = new System.Text.StringBuilder();
135+
int lastIndex = 0;
136+
int i = 0;
137+
bool inBracket = false;
138+
139+
while (i < key.Length)
140+
{
141+
char c = key[i];
142+
143+
if (c == '[')
144+
{
145+
// Format the segment before the bracket
146+
if (i > lastIndex)
147+
{
148+
string segment = key.Substring(lastIndex, i - lastIndex);
149+
string formattedSegment = SerializerOptions!.PropertyNamingPolicy!.ConvertName(segment);
150+
result.Append(formattedSegment);
151+
}
152+
153+
// Start collecting the bracket part
154+
inBracket = true;
155+
result.Append(c);
156+
lastIndex = i + 1;
157+
}
158+
else if (c == ']')
159+
{
160+
// Add the content inside the bracket as-is
161+
if (i > lastIndex)
162+
{
163+
string segment = key.Substring(lastIndex, i - lastIndex);
164+
result.Append(segment);
165+
}
166+
result.Append(c);
167+
inBracket = false;
168+
lastIndex = i + 1;
169+
}
170+
else if (c == '.' && !inBracket)
171+
{
172+
// Format the segment before the dot
173+
if (i > lastIndex)
174+
{
175+
string segment = key.Substring(lastIndex, i - lastIndex);
176+
string formattedSegment = SerializerOptions!.PropertyNamingPolicy!.ConvertName(segment);
177+
result.Append(formattedSegment);
178+
}
179+
result.Append(c);
180+
lastIndex = i + 1;
181+
}
182+
183+
i++;
184+
}
185+
186+
// Format the last segment if there is one
187+
if (lastIndex < key.Length)
188+
{
189+
string segment = key.Substring(lastIndex);
190+
if (!inBracket)
191+
{
192+
segment = SerializerOptions!.PropertyNamingPolicy!.ConvertName(segment);
193+
}
194+
result.Append(segment);
195+
}
196+
197+
return result.ToString();
99198
}
100199
}

0 commit comments

Comments
 (0)