Custom serialization for ValidationProblemDetails to allign paths in custom and mvc error messages (#19111)
* Rowrked #18771 with a more brute force solution * Fixes class name. Adds unit test verifying behaviour. * Corrected test name. --------- Co-authored-by: Kenn Jacobsen <kja@umbraco.dk> Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -33,6 +33,7 @@ public class ConfigureUmbracoBackofficeJsonOptions : IConfigureNamedOptions<Json
|
|||||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||||
options.JsonSerializerOptions.Converters.Add(new JsonUdiConverter());
|
options.JsonSerializerOptions.Converters.Add(new JsonUdiConverter());
|
||||||
options.JsonSerializerOptions.Converters.Add(new JsonUdiRangeConverter());
|
options.JsonSerializerOptions.Converters.Add(new JsonUdiRangeConverter());
|
||||||
|
options.JsonSerializerOptions.Converters.Add(new ValidationProblemDetailsConverter());
|
||||||
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());
|
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());
|
||||||
|
|
||||||
options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
|
options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Umbraco.Cms.Core;
|
||||||
|
using Umbraco.Extensions;
|
||||||
|
|
||||||
|
namespace Umbraco.Cms.Api.Management.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom JSON converter registered via <see cref="ConfigureUmbracoBackofficeJsonOptions"/> to serialize <see cref="ValidationProblemDetails" /> to JSON.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ValidationProblemDetailsConverter : JsonConverter<ValidationProblemDetails>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ValidationProblemDetails? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Write(Utf8JsonWriter writer, ValidationProblemDetails value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WriteString("type", value.Type);
|
||||||
|
writer.WriteString("title", value.Title);
|
||||||
|
|
||||||
|
if (value.Status.HasValue)
|
||||||
|
{
|
||||||
|
writer.WriteNumber("status", value.Status.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Detail is not null)
|
||||||
|
{
|
||||||
|
writer.WriteString("detail", value.Detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Instance is not null)
|
||||||
|
{
|
||||||
|
writer.WriteString("instance", value.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Errors.Any())
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("errors");
|
||||||
|
writer.WriteStartArray();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, string[]> error in value.Errors)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WritePropertyName($"$.{string.Join(".", error.Key.Split(Constants.CharArrays.Period).Select(s => s.ToFirstLowerInvariant()))}");
|
||||||
|
writer.WriteStartArray();
|
||||||
|
|
||||||
|
foreach (var errorDetails in error.Value)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(errorDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndArray();
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Extensions.TryGetValue("traceId", out var traceId) && traceId is string traceIdValue)
|
||||||
|
{
|
||||||
|
writer.WriteString("traceId", traceIdValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,17 @@ public class BackOfficeSerializationTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Will_Serialize_ValidationProblemDetails_To_Casing_Aligned_With_Mvc()
|
||||||
|
{
|
||||||
|
var objectToSerialize = new TestValueWithValidationProblemDetail();
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(objectToSerialize, jsonOptions.JsonSerializerOptions);
|
||||||
|
|
||||||
|
var expectedJson = "{\"problemDetails\":{\"type\":\"Test type\",\"title\":\"Test title\",\"status\":400,\"detail\":\"Test detail\",\"instance\":\"Test instance\",\"errors\":[{\"$.testError1\":[\"Test error 1a\",\"Test error 1b\"]},{\"$.testError2\":[\"Test error 2a\"]},{\"$.testError3.testError3a\":[\"Test error 3b\"]}],\"traceId\":\"traceValue\"}}";
|
||||||
|
Assert.AreEqual(expectedJson, json);
|
||||||
|
}
|
||||||
|
|
||||||
private static NestedJsonTestValue CreateNestedObject(int levels)
|
private static NestedJsonTestValue CreateNestedObject(int levels)
|
||||||
{
|
{
|
||||||
var root = new NestedJsonTestValue { Level = 1 };
|
var root = new NestedJsonTestValue { Level = 1 };
|
||||||
@@ -77,4 +88,27 @@ public class BackOfficeSerializationTests
|
|||||||
|
|
||||||
public NestedJsonTestValue? Inner { get; set; }
|
public NestedJsonTestValue? Inner { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestValueWithValidationProblemDetail
|
||||||
|
{
|
||||||
|
public ValidationProblemDetails ProblemDetails { get; set; } = new()
|
||||||
|
{
|
||||||
|
Title = "Test title",
|
||||||
|
Detail = "Test detail",
|
||||||
|
Status = 400,
|
||||||
|
Type = "Test type",
|
||||||
|
Instance = "Test instance",
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
["traceId"] = "traceValue",
|
||||||
|
["someOtherExtension"] = "someOtherExtensionValue",
|
||||||
|
},
|
||||||
|
Errors =
|
||||||
|
{
|
||||||
|
["TestError1"] = ["Test error 1a", "Test error 1b"],
|
||||||
|
["TestError2"] = ["Test error 2a"],
|
||||||
|
["TestError3.TestError3a"] = ["Test error 3b"],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user