JsonReaderException - неожиданный символ, обнаруженный при синтаксическом анализе значения - PullRequest
2 голосов
/ 23 января 2020

Я сталкивался со многими вопросами в 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?

Ответы [ 2 ]

2 голосов
/ 23 января 2020

Это вызвано поведением метода 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 и замените ошибка

При синтаксическом анализе значения обнаружен непредвиденный символ: T. Путь 'parent.booleanChild', строка 0, позиция 0

Опция 3: Установить AllowInputFormatterExceptionMessages в false и в InvalidModelStateResponseFactory предположим, что любые пустые сообщения будут вызваны ошибками сериализации.

Я не отмечаю это как ответьте, поскольку я уверен, что у кого-то еще будет лучшая идея.

Я поднял проблему GitHub , которая предлагает то, что я думаю, может быть решением.

Другие вопросы SO Я нашел:

ASP. NET Обработка ядра JSON Проблемы десериализации

* 10 44 * Переопределение сообщений об ошибках ModelBindingMessageProvider

1 голос
/ 19 апреля 2020

Я достиг чего-то похожего на то, на что, я полагаю, вы ссылаетесь в своем вопросе, хотя это все еще довольно странно. И, вероятно, очень ненадежный.

Я, честно говоря, не могу поверить, что это не поддерживается (есть много причин, в которых я нуждаюсь, включая безопасность и ограничение сложности кодовой базы) - это особенно озадачивает, как новый System.Text.Json.Serialization еще не поддерживает многие функции, которые предлагает Newtonsoft Json.

Моя попытка повторяет что-то похожее на вашу Option 2, но не соответствует строковому шаблону. он полагается на то, что вы сможете извлечь подходящее сообщение из исключения, которое было сгенерировано во время привязки (и действительно, существующее исключение не будет работать, если сообщения не генерируются из исключений.)

Я сделал это в Asp. Net Core 3, хотя вы, вероятно, могли бы сделать нечто подобное в 2.2. Я решил не переопределять JsonInputFormatter, потому что я обнаружил, что для этого потребуется также переопределить большую часть соединения.

В моем случае все ошибки проверки генерируются указанным базовым классом исключений c. у которого достаточно контекста, чтобы определить, какой тип ответа должен быть предоставлен пользователю. Исключение будет помещено в исключение JsonSerializationException и отклонено JsonInputFormatter, поскольку оно считает, что это исключение времени выполнения. Однако, как вы указали, событие Error в JsonSerializerSettings позволяет нам получить доступ к этому исключению.

Я сохраняю исключение и путь, предоставленный Json. Net, в хранилище, ограниченное запросом, в моем PO C case Я передаю ссылку IServiceProvider, полученную через набор сервисов, использованный при запуске. Таким образом, мы можем получить ссылку на оригинальный HttpContext, который затем может быть выбран InvalidModelStateResponseFactory.

Мой код запуска выглядит следующим образом: -

            services
            .AddControllers()
            .ConfigureApiBehaviorOptions(options => options.InvalidModelStateResponseFactory = HandleTypedErrors)
            .AddNewtonsoftJson(options => options.ApplySerializationErrorRecording(services));
    }

    private static IActionResult HandleTypedErrors(ActionContext actionContext)
    {
        if (actionContext.ModelState.IsValid)
        {
            return null;
        }

        var errors = actionContext.ModelState
            .Select(modelError => new
            {
                State = modelError,
                Exception = actionContext.HttpContext.RetrieveSerializationException<ErrorException>(modelError.Key)
            })
            .Where(state => state.Exception != null)
            .Select(modelError => new
            {
                ErrorField = modelError.State.Key,
                ErrorCode = modelError.Exception.ErrorCode,
                ErrorDescription = modelError.Exception.Message
            })
            .ToList();

        return errors.Any() ? new BadRequestObjectResult(errors.ToList()) : null;
    }

Запись ошибок применения выглядит примерно так: -

public static MvcNewtonsoftJsonOptions ApplySerializationErrorRecording(this MvcNewtonsoftJsonOptions options, IServiceCollection services)
        {
            options.SerializerSettings.Error += (sender, args) =>
            {
                var provider = services.BuildServiceProvider();
                var context = provider.GetRequiredService<IHttpContextAccessor>().HttpContext;

                if (context == null || !(args.ErrorContext.Error is JsonException) ||
                    !(args.ErrorContext.Error.InnerException is ErrorException ex))
                    return;

                var errors = context.Items["Error"] as Dictionary<string, ErrorException> ??
                             new Dictionary<string, ErrorException>();
                errors.Add(args.ErrorContext.Path, ex);
                context.Items["Error"] = errors;
            };

            return options;
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...