Skip to content

Migrating from Newtonsoft.Json to System.Text.Json reduces quality of validation errors #38271

Closed
@SigmundVik

Description

@SigmundVik

This issue is related to this dotnet/runtime issue.

The examples below use a vanilla ASP.NET Core application with this controller:

namespace ApplicationName.Controllers
{
    using System;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;

    // This of course belongs to a different file and namespace:
    public class Model
    {
        public Guid Id { get; set; }
        public int Integer { get; set; }
        public string String { get; set; }
    }

    [Produces("application/json")]
    [Consumes("application/json")]
    [Route("test")]
    [ApiController]
    public class Controller : ControllerBase
    {
        [HttpPost]
        public async Task<IActionResult> PostAsync([FromBody] Model model)
        {
            return Ok(model);
        }
    }
}

In each example below, the body for a POST request is listed first. Then comes the response when using Newtonsoft.JSON for doing the model binding, and then the response when using System.Text.Json.

Example 1:

{
    "id": "00000000-0000-0000-0000-00000000000x",
    "integer": 2,
    "string": "2"
}

Response using Newtonsoft.JSON:

{
    "errors": {
        "id": [
            "Error converting value \"00000000-0000-0000-0000-00000000000x\" to type 'System.Guid'. Path 'id', line 2, position 48."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|ac238c1a-4b2c24d7c287f27d."
}

Response using System.Text.Json:

{
    "errors": {
        "$": [
            "The JSON value could not be converted to ApplicationName.Controllers.Model. Path: $ | LineNumber: 1 | BytePositionInLine: 48."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "detail": null,
    "instance": null,
    "traceId": "|29c80116-4896648215ca6161."
}

Example 2:

{
    "id": "00000000-0000-0000-0000-00000000000a",
    "integer": 2x,
    "string": "2"
}

Response using Newtonsoft.JSON:

{
    "errors": {
        "integer": [
            "Input string '2x' is not a valid integer. Path 'integer', line 3, position 17."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|ac238c1b-4b2c24d7c287f27d."
}

Response using System.Text.Json:

{
    "errors": {
        "$": [
            "'x' is an invalid end of a number. Expected a delimiter. Path: $ | LineNumber: 2 | BytePositionInLine: 16."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "detail": null,
    "instance": null,
    "traceId": "|29c80117-4896648215ca6161."
}

Example 3:

{
    "id": "00000000-0000-0000-0000-00000000000a",
    "integer": 2,
    "string": 2 x
}

Response using Newtonsoft.JSON:

{
    "errors": {
        "string": [
            "After parsing a value an unexpected character was encountered: x. Path 'string', line 4, position 16."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|ac238c1d-4b2c24d7c287f27d."
}

Response using System.Text.Json:

{
    "errors": {
        "$": [
            "The JSON value could not be converted to ApplicationName.Controllers.Model. Path: $ | LineNumber: 3 | BytePositionInLine: 15."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "detail": null,
    "instance": null,
    "traceId": "|a88bc7ad-44774eaf80acd428."
}

Example 4:

{
    "id": "00000000-0000-0000-0000-00000000000a",
    "integer": 2,
    "string": 2
}

Response using Newtonsoft.JSON (no error in this case due to lenient parsing):

{
    "id": "00000000-0000-0000-0000-00000000000a",
    "integer": 2,
    "string": "2"
}

Response using System.Text.Json (strict is better, but this could still be improved):

{
    "errors": {
        "$": [
            "The JSON value could not be converted to ApplicationName.Controllers.Model. Path: $ | LineNumber: 3 | BytePositionInLine: 15."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "detail": null,
    "instance": null,
    "traceId": "|29c80118-4896648215ca6161."
}

The responses when using System.Text.Json consistently makes it harder for the end user to figure out what is wrong. It would be great if the validation errors when using System.Text.Json would be of the same quality as when using Newtonsoft.Json.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions