skip to Main Content

I am integrating some API and getting variant response for error field

{ "errors" : "amar" } 
{ "errors" : "akbar" }
{ "error" : "anthony" }

My error handling class has one property string Error.

How can I deserialize in an efficient way in that without try catch? I am using .NET Core 8.0

For array of strings, I can have comma-separated errors.

My model for class looks like this at the moment:

public record ApiResponseError([property: JsonPropertyName("error")] string Error)

3

Answers


  1. According to your description, you could create a custom json converter to handle the json for the APIERROR record.

    You could create the converter like below:

    public class ApiResponseErrorConverter : JsonConverter<ApiResponseError>
    {
        public override ApiResponseError Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            string errorMessage = null;
    
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }
    
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    string propertyName = reader.GetString();
    
                    if (propertyName == "error" || propertyName == "errors")
                    {
                        reader.Read();
                        if (reader.TokenType == JsonTokenType.String)
                        {
                            errorMessage = reader.GetString();
                        }
                        else
                        {
                            throw new JsonException();
                        }
                    }
                }
                else if (reader.TokenType == JsonTokenType.EndObject)
                {
                    break;
                }
            }
    
            if (errorMessage == null)
            {
                throw new JsonException("The JSON does not contain an 'error' or 'errors' property.");
            }
    
            return new ApiResponseError(errorMessage);
        }
    
        public override void Write(Utf8JsonWriter writer, ApiResponseError value, JsonSerializerOptions options)
        {
            writer.WriteStartObject();
            writer.WriteString("error", value.Error);
            writer.WriteEndObject();
        }
    }
    

    Register it inside the program.cs

    builder.Services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new ApiResponseErrorConverter());
    });
    

    Test with the controller

    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        [HttpPost]
        public IActionResult Post(ApiResponseError error)
        {
            return Ok(new { Error = error });
        }
    }
    

    Result:

    enter image description here

    and

    enter image description here

    Login or Signup to reply.
  2. You could create a custom converter. Would be something like this

    public class ApiResponseErrorJsonConverter : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            return true;
        }
    
        public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var converter = new ApiResponseErrorJsonConverterInner();
    
            return converter;
        }
    
        private class ApiResponseErrorJsonConverterInner : JsonConverter<ApiResponseError>
        {
            public override ApiResponseError? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                if (reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }
    
                string? error = null;
    
                while (reader.Read())
                {
                    if (reader.TokenType == JsonTokenType.EndObject)
                    {
                        break;
                    }
    
                    if (reader.TokenType != JsonTokenType.PropertyName)
                    {
                        throw new JsonException();
                    }
    
                    var prop = reader.GetString();
                    reader.Read();
    
                    switch (prop)
                    {
                        case "error":
                            error = reader.GetString();
                            break;
                        case "errors":
                            error = reader.GetString(); // Not sure if multiple errors are comma separated from API already?
                            break;
    
                        default:
                            break;
                    }
                }
    
                return new(error ?? string.Empty);
            }
    
            public override void Write(Utf8JsonWriter writer, ApiResponseError value, JsonSerializerOptions options)
            {
                writer.WriteStartObject();
                writer.WriteString("error", value.Error);
                writer.WriteEndObject();
            }
        }
    }
    

    Finally decorate your response class with the JsonConverterAttribute

    [JsonConverter(typeof(ApiResponseErrorJsonConverter))]
    public record ApiResponseError(string Error);
    
    Login or Signup to reply.
  3. In .Net 8 some new attributes came into play, which help to solve this without writing your own converter:

    public class MyErrorClass
    {
        public List<string> Errors { get; init; } = [];
    
        [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
        [JsonPropertyName("errors")]
        [JsonInclude]
        private ICollection<string> _multipleErrors { get { return Errors; } }
    
        [JsonPropertyName("error")]
        [JsonInclude]
        private string _singleError { set { Errors.Add(value); } }
    
        public override string ToString()
        {
            return $"Errors: {string.Join(", ", Errors)}";
        }
    }
    

    You can test the different JSONs with this little program:

    public static class Program
    {
        private static void Main()
        {
            var error = JsonSerializer.Deserialize<MyErrorClass>("""{"error":"Some single error"}""");
            Console.WriteLine(error);
    
            error = JsonSerializer.Deserialize<MyErrorClass>("""{"errors": ["Some array single error"]}""");
            Console.WriteLine(error);
    
            error = JsonSerializer.Deserialize<MyErrorClass>("""{"errors": ["Some array error", "Another array error"]}""");
            Console.WriteLine(error);
        }
    }
    

    This would make the desired usage of the property better, but unfortunately this doesn’t work:

    [JsonObjectCreationHandling(JsonObjectCreationHandling.Replace)]
    [JsonPropertyName("errors")]
    [JsonInclude]
    private ICollection<string> _multipleErrors { set { Errors.AddRange(value); } }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search