feat: add json property path supports for object keys #7

Merged
louis9902 merged 1 commit from feature/property_key_json_support into main 2026-05-30 20:20:56 +00:00
3 changed files with 58 additions and 0 deletions
Showing only changes of commit eff887b49f - Show all commits

feat: add json property path supports for object keys
All checks were successful
default / dotnet-default-workflow (pull_request) Successful in 2m4s
default / dotnet-default-workflow (push) Successful in 2m1s

Louis Seubert 2026-05-30 21:17:14 +02:00
Signed by: louis9902
GPG key ID: 4B9DB28F826553BD

View file

@ -41,6 +41,8 @@ To have a consistent experience across all packages, some public interfaces have
### Added
- **request.validation:** Support `PropertyPath` JSON converter for string values and dictionary property names
### Changed
### Removed

View file

@ -12,6 +12,19 @@ internal sealed class PropertyPathTests
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
private static readonly JsonSerializerOptions CamelCaseDictionaryKeyJsonOptions = new()
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
};
[Test]
public async Task I_can_serialize_property_paths_as_json_strings_using_the_json_naming_policy()
{
var json = JsonSerializer.Serialize((PropertyPath)"Address.Street", CamelCaseJsonOptions);
await Assert.That(json).IsEqualTo(/*lang=json,strict*/ "\"address.street\"");
}
[Test]
public async Task I_can_serialize_property_paths_using_the_json_naming_policy()
{
@ -54,6 +67,32 @@ internal sealed class PropertyPathTests
await Assert.That(json).IsEqualTo(/*lang=json,strict*/ """{"propertyPath":"matrix[1][2].value","severity":0,"message":"Value is required.","code":null,"attemptedValue":null}""");
}
[Test]
public async Task I_can_serialize_property_paths_as_dictionary_keys_using_the_dictionary_key_policy()
{
Dictionary<PropertyPath, string> errors = new()
{
["Address.Street"] = "Street is required.",
};
var json = JsonSerializer.Serialize(errors, CamelCaseDictionaryKeyJsonOptions);
await Assert.That(json).IsEqualTo(/*lang=json,strict*/ """{"address.street":"Street is required."}""");
}
[Test]
public async Task I_can_deserialize_property_paths_from_dictionary_keys()
{
var json = /*lang=json,strict*/ """{"Address.Street":"Street is required."}""";
var errors = JsonSerializer.Deserialize<Dictionary<PropertyPath, string>>(json);
await Assert.That(errors).IsNotNull();
await Assert.That(errors!.Count).IsEqualTo(1);
await Assert.That(errors.ContainsKey("Address.Street")).IsTrue();
await Assert.That(errors["Address.Street"]).IsEqualTo("Street is required.");
}
[Test]
public async Task I_can_iterate_and_index_segments()
{

View file

@ -399,9 +399,26 @@ internal sealed class PropertyPathJsonConverter : JsonConverter<PropertyPath>
throw new JsonException($"Expected {nameof(JsonTokenType.String)} but got {reader.TokenType}.");
}
/// <inheritdoc />
public override PropertyPath ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType is JsonTokenType.PropertyName)
{
return new PropertyPath(reader.GetString()!);
}
throw new JsonException($"Expected {nameof(JsonTokenType.PropertyName)} but got {reader.TokenType}.");
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, PropertyPath value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToJsonName(options.PropertyNamingPolicy));
}
/// <inheritdoc />
public override void WriteAsPropertyName(Utf8JsonWriter writer, PropertyPath value, JsonSerializerOptions options)
{
writer.WritePropertyName(value.ToJsonName(options.DictionaryKeyPolicy));
}
}