如何从 Utf8JsonReader 获取 属性 路径?

How do I get the property path from Utf8JsonReader?

使用 NewtonSoft,我们可以通过 reader.Path 获取路径。 System.Text.Json没有这个。

namespace API.JsonConverters
{
    using System;
    using System.Text.Json;
    using System.Text.Json.Serialization;

    /// <summary>
    /// Use DateTime.Parse to replicate how Newtonsoft worked.
    /// </summary>
    /// <remarks>https://docs.microsoft.com/en-us/dotnet/standard/datetime/system-text-json-support</remarks>
    public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
    {
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            try
            {
                return DateTime.Parse(reader.GetString(), styles: System.Globalization.DateTimeStyles.RoundtripKind);
            }
            catch (FormatException)
            {
                // We have to do this to have the Path variable auto populated so when the middleware catches the error, it will properly populate the ModelState errors.
                // https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#error-handling
                // https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs#L79
                throw new JsonException("Invalid DateTime. Please use RoundTripKind (MM-DD-YYYY) - https://docs.microsoft.com/en-us/dotnet/standard/base-types/how-to-round-trip-date-and-time-values");
            }
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString("o"));
        }
    }
}

我怎样才能访问当前路径,以便我可以从自定义 [=15= 的 Read() 方法中抛出一个包含自定义消息和 Path 的异常]?

JsonConverter, you want to throw a custom exception with a custom message and also include JSONPath information. As explained by the docs, System.Text.Json only appends path information to an exception of type JsonException 内 - 并且仅当异常没有消息时。那么,怎么才能包含路径信息呢?

执行此操作的明显方法是从 JsonConverter<T>.Read() and pass it to your exception's constructor. Unfortunately, System.Text.Json does not make the path available to Read() or Write(). This can be confirmed by checking the reference source. Utf8JsonReader currently does not even know the path. All it knows is the stack of container types (object or array) using a BitStack member Utf8JsonReader._bitStack, which is the minimum necessary to handle its state transitions correctly when exiting a nested container. JsonSerializer does track the current stack, via the ReadStack ref struct which has a JsonPath() 方法中获取当前路径。不幸的是 ReadStack 是内部的,永远不会暴露给应用程序或 Utf8JsonReader.

作为解决方法,您可以创建嵌套异常,其中内部异常是您所需的异常类型,外部异常是 JsonException,序列化程序将在其中填充路径。以下是如何执行此操作的一个示例:

public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    try
    {
        return DateTime.Parse(reader.GetString(), 
                              styles: System.Globalization.DateTimeStyles.RoundtripKind); 
    }
    catch (FormatException)
    {
        var innerEx = new ProblemDetailsException("Invalid DateTime. Please use RoundTripKind (MM-DD-YYYY) - https://docs.microsoft.com/en-us/dotnet/standard/base-types/how-to-round-trip-date-and-time-values");
        throw new JsonException(null, innerEx);
    }
}

然后在更高的层次上你可以抓住 JsonException 并抛出一个外部 ProblemDetailsException,例如像这样:

public class JsonExtensions
{
    public static T Deserialize<T>(string json, JsonSerializerOptions options = default)
    {
        try
        {
            return JsonSerializer.Deserialize<T>(json, options);
        }
        catch (JsonException ex) when (ex.InnerException is ProblemDetailsException innerException)
        {
            var finalException = new ProblemDetailsException(innerException.Message, ex.Path, ex);
            throw finalException;
        }
    }
}

备注:

  • 这里我假设 ProblemDetailsException 看起来像:

    public class ProblemDetailsException : System.Exception
    {
        public ProblemDetailsException(string message) : base(message) { }
        public ProblemDetailsException(string message, Exception inner) : base(message, inner) { }
        public ProblemDetailsException(string message, string path) : base(message) => this.Path = path;
        public ProblemDetailsException(string message, string path, Exception inner) : base(message, inner) => this.Path = path;
    
        public string Path { get; }
    }
    
  • 您可以考虑使用 CultureInfo.InvariantCulture:

    解析您的 DateTime
    return DateTime.Parse(reader.GetString(), 
                          styles: System.Globalization.DateTimeStyles.RoundtripKind, 
                          provider: System.Globalization.CultureInfo.InvariantCulture);
    

    如目前所写,您的转换器在不同区域设置中的功能会有所不同。或者,如果您真的想在当前语言环境中进行解析,请在您的代码中明确说明:

    return DateTime.Parse(reader.GetString(), 
                          styles: System.Globalization.DateTimeStyles.RoundtripKind, 
                          provider: System.Globalization.CultureInfo.CurrentCulture);
    

演示 fiddle here.