Search code examples
c#.net-corejson.netblazordeserialization

JsonConvert.DeserializeObject throwing error after adding AddNewtonsoftJson()


For using Patch in my Blazor application, we have used builder.Services.AddControllers().AddNewtonsoftJson(). But after adding it everything works fine if we dont return valdiationerror into response. When we return validation response, it throws an error as below,

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
  Unhandled exception rendering component: Unable to find a constructor to use for type System.ComponentModel.DataAnnotations.ValidationResult. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'validationResult.validationResults[0].memberNames', line 1, position 493.
Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type System.ComponentModel.DataAnnotations.ValidationResult. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'validationResult.validationResults[0].memberNames', line 1, position 493.
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)

This is what I have done for grabbing response(please ignore the error at Mymodel, I changed it for screenshot only). It breaks at DeserializeObject only when res has validationresults.

response = await Http.PostAsJsonAsync(Constants.uri, editModel);
var res = response.Content.ReadAsStringAsync().Result;
var validationResponse = JsonConvert.DeserializeObject<Mymodel>(res);

And when I we will remove .AddNewtonsoftJson(), it returns proper validation message but Patch wont work!

Mymodel looks like this:

public class MymodelResponse : BaseResponse
{
    public int MymodelId { get; set; }
    public string? Phone { get; set; }
    public long CreatedBy { get; set; }
    public DateTime CreatedDate { get; set; }
    public long? ModifiedBy { get; set; }
    public DateTime? ModifiedDate { get; set; }
    public bool IsDeleted { get; set; }
    public string? Notes { get; set; }

    public MymodelResponse(List<ValidationResult> validationResults) : base(validationResults)
    {

    }
}

And BaseResponse has a property

public List<ValidationResult> ValidationResults  { get; set; }

Which is set in the constructor.


Solution

  • The problem is as described in the error message: Json.NET is unable to choose a constructor for ValidationResult because it has two public parameterized constructors and no default constructor, and Json.NET has no logic to prefer one parameterized constructor over another.

    Since ValidationResult is a simple type with two properties, you could create a custom JsonConverter<ValidationResult> for it:

    public class ValidationResultConverter : JsonConverter<ValidationResult>
    {
        record DTO(string ErrorMessage, IEnumerable<string> MemberNames);       
        public override ValidationResult ReadJson(JsonReader reader, Type objectType, ValidationResult existingValue, bool hasExistingValue, JsonSerializer serializer) =>
            serializer.Deserialize<DTO>(reader) is {} dto
                ? new ValidationResult(dto.ErrorMessage, dto.MemberNames)
                : null;
        public override void WriteJson(JsonWriter writer, ValidationResult value, JsonSerializer serializer) => 
            serializer.Serialize(writer, new DTO(value.ErrorMessage, value.MemberNames));
    }
    

    Then add the converter to JsonSerializerSettings.Converters and deserialize like so:

    var settings = new JsonSerializerSettings
    {
        Converters = { new ValidationResultConverter() },
    };
    var validationResponse = JsonConvert.DeserializeObject<Mymodel>(res, settings);
    

    And/or add it in AddNewtonsoftJson() as follows

    .AddNewtonsoftJson(opts =>
    {
        opts.SerializerSettings.Converters.Add(new ValidationResultConverter());
    });
    

    Notes:

    Demo fiddle here.