c# - JsonReaderException - 解析值时遇到意外字符

标签 c# asp.net-core json.net

我在 StackOverflow 上遇到过很多关于这个错误的问题,但没有一个能达到我的目的。

我想做的是将下面的错误消息数组翻译成更易读的内容

{
    "parent.booleanChild": [
        "Unexpected character encountered while parsing value: T. Path 'parent.booleanChild', line 0, position 0",
        "Unexpected character encountered while parsing value: r. Path 'parent.booleanChild', line 0, position 0"
    ]
}

期望的结果

{
    "parent.booleanChild": [
        "Value 'True' is not valid, only 'true', 'false' and 'null' are allowed."
    ]
}

示例请求

{
    "parent": {
        "booleanChild": True
    }
}

我已尝试实现自定义 JsonConverter,但发现在执行转换器之前引发了 JsonReaderException

有没有人实现了类似的东西,使他们能够在不实现自定义 IInputFormatter 的情况下生成更有意义和可读的错误消息?

最佳答案

这是由 JsonInputFormatter 的行为引起的 ReadRequestBodyAsync方法。

我遇到了一些使用自定义错误消息的选项,但没有一个是优雅的。

选项 1:覆盖 ReadRequestBody 以添加固定的错误消息。

public class CustomJsonInputFormatter : JsonInputFormatter
{
    private readonly IArrayPool<char> charPool;
    private readonly MvcOptions options;

    public CustomJsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, MvcOptions options, MvcJsonOptions jsonOptions)
        : base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
    {
        this.charPool = new JsonArrayPool<char>(charPool);
        this.options = options;
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context,
        Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (encoding == null)
        {
            throw new ArgumentNullException(nameof(encoding));
        }

        var request = context.HttpContext.Request;

        var suppressInputFormatterBuffering = options?.SuppressInputFormatterBuffering ?? false;

        if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
        {
            // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
            // read everything into a buffer, and then seek back to the beginning.
            request.EnableBuffering();
            Debug.Assert(request.Body.CanSeek);

            await request.Body.DrainAsync(CancellationToken.None);
            request.Body.Seek(0L, SeekOrigin.Begin);
        }

        using (var streamReader = context.ReaderFactory(request.Body, encoding))
        {
            using (var jsonReader = new JsonTextReader(streamReader))
            {
                jsonReader.ArrayPool = charPool;
                jsonReader.CloseInput = false;

                var successful = true;
                Exception exception = null;
                void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
                {
                    successful = false;

                    var path = eventArgs.ErrorContext.Path;

                    var key = ModelNames.CreatePropertyModelName(context.ModelName, path);
                    context.ModelState.TryAddModelError(key, $"Invalid value specified for {path}");
                    eventArgs.ErrorContext.Handled = true;
                }

                var type = context.ModelType;
                var jsonSerializer = CreateJsonSerializer();
                jsonSerializer.Error += ErrorHandler;
                object model;
                try
                {
                    model = jsonSerializer.Deserialize(jsonReader, type);
                }
                finally
                {
                    // Clean up the error handler since CreateJsonSerializer() pools instances.
                    jsonSerializer.Error -= ErrorHandler;
                    ReleaseJsonSerializer(jsonSerializer);
                }

                if (successful)
                {
                    if (model == null && !context.TreatEmptyInputAsDefaultValue)
                    {
                        // Some nonempty inputs might deserialize as null, for example whitespace,
                        // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                        // be notified that we don't regard this as a real input so it can register
                        // a model binding error.
                        return InputFormatterResult.NoValue();
                    }
                    else
                    {
                        return InputFormatterResult.Success(model);
                    }
                }

                if (!(exception is JsonException || exception is OverflowException))
                {
                    var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
                    exceptionDispatchInfo.Throw();
                }

                return InputFormatterResult.Failure();
            }
        }
    }
}

选项 2:在 InvalidModelStateResponseFactory 中执行模式匹配并替换错误

Unexpected character encountered while parsing value: T. Path 'parent.booleanChild', line 0, position 0

选项 3:设置 AllowInputFormatterExceptionMessages为假并在 InvalidModelStateResponseFactory 中做出假设任何空白消息都是由于序列化错误造成的。

我不会将此标记为答案,因为我相信其他人会有更好的主意。

我提出了一个GitHub issue它提出了我认为可能是解决方案的方法。

我发现的其他SO问题:

ASP.NET Core handling JSON deserialization problems

Overriding ModelBindingMessageProvider error messages

关于c# - JsonReaderException - 解析值时遇到意外字符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59877835/

相关文章:

c# - 无法绑定(bind)到 DataContext

c# - 测试验证方法

c# - 连接 .NET Core 配置

c# - 从 C# 中的 JSON 字符串中获取特定值

arrays - 使用 Newtonsoft Json.NET 循环使用动态对象 ID 的非数组 JSON

c# - 反序列化泛型类型

c# - 'Environment.SpecialFolder.SystemX86' 在 32 位 Windows 操作系统上会返回什么?

c# - 如何在 EPPlus 中使用 C# 删除 AutoFilter

asp.net-core - Serilog 日志记录 web-api 方法,在中间件中添加上下文属性

c# - 双击启动 Windows 服务