asp.net-coremodel-bindingsystem.text.json

How to enable escaped JSON string parsing in asp.net core?


When I send a request to the endpoint with an escaped JSON string it throws an exception "JSON value could not be converted to Test.TestClass . Path: $ | LineNumber: 0 | BytePositionInLine: 90.\r\n"". How can I enable escaped JSON string parsing in ASP.NET 5? I am using System.Text.Json (v 6.0.1). Tried updating to (v8) but no luck. Also tried using AddNewtonsoftJson() but still got the same result.

Creating a custom JSON converter works but need a solution which applies globally.

The curl for my request with the escaped JSON string.

curl --location 'https://localhost:8080/api/testEndpoint' \
--header 'charset: utf-8' \
--header 'Content-Type: application/json' \
--data '"{\"testId\":0,\"created\":\"2024-03-09T14:17:19.9837955Z\"}"'

The model class

public class TestClass 
{
  public int TestId {get;set;}
  public DateTime Created {get;set;}
}

Solution

  • The issue was the client was double serializing the the request JSON. Thanks @Panagiotis Kanavos for pointing out.

    However, until the client side issue is resolved I created a custom JSON converter to temporarily handle the issue. And posting it since it could be useful to anyone else for a different purpose.

    In the below code just implement a blank interface 'IJSONStringDto' to the endpoint dtos and the converter will handle the normal JSON parsing and double serialized JSON parsing.

        public class JSONStringConverterFactory : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            if (typeof(IJSONStringDto).IsAssignableFrom(typeToConvert))
            {
                return true;
            }
    
            return false;
        }
    
        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            JsonConverter converter = (JsonConverter)Activator.CreateInstance(
               typeof(JSONStringObjectConverter<>).MakeGenericType(
                   new Type[] { typeToConvert }),
               BindingFlags.Instance | BindingFlags.Public,
               binder: null,
               args: new object[] { options },
               culture: null);
    
            return converter;
        }
    
        private class JSONStringObjectConverter<TJSONStringType> : JsonConverter<TJSONStringType> where TJSONStringType: IJSONStringDto
        {
            public JSONStringObjectConverter(JsonSerializerOptions options)
            {
    
            }
    
            public override TJSONStringType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                if (reader.TokenType == JsonTokenType.String)
                {
                    return JsonSerializer.Deserialize<TJSONStringType>(reader.GetString());
                }
    
                using (var jsonDoc = JsonDocument.ParseValue(ref reader))
                {
                    return JsonSerializer.Deserialize<TJSONStringType>(jsonDoc.RootElement.GetRawText());
                }
            }
    
            public override void Write(Utf8JsonWriter writer, TJSONStringType value, JsonSerializerOptions options)
            {
                throw new NotImplementedException();
            }
        }
    }
    

    Then register the converter

    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JSONStringConverterFactory());
        
    });