Zhenyu Jing
Zhenyu Jing

Reputation: 21

Custom Json.net JsonConverter with Constructor warning "No parameterless constructor defined for this object"

When I use newtosoft.json in netcore 2.1, I come across a problem. When I add a Constructor in my ApiErrorConverter class which inherits from JsonConverter. It's warning me

“No parameterless constructor defined for this object.”

When I remove the Constructor in my ApiErrorConverter class, it will work well. But I want to pass some value into the JsonConverter to implement converting JSON map to C# object flexibly. Here is my code:

Payload

string payload2 ="{\"statusCode\":123,\"name\":\"error_code\",\"description\":\"The Message\",\"item\":{\"ahah\":\"cl\"}}";

Model

public class ApiError
{
    [JsonProperty("error")]
    public string Error { get; set; }
    [JsonProperty("errorCode")]
    public string ErrorCode { get; set; }
    [JsonProperty("message")]
    public string Message { get; set; }
    [JsonProperty("statusCode")]
    public int StatusCode { get; set; }
}

Model converter

public class ApiErrorConverter : JsonConverter
{
    Dictionary<string, string> _propertyMappings = new Dictionary<string,
    string>();
    public ApiErrorConverter(Dictionary<string, string> dataparam)
    {
        propertyMappings = dataparam;
    }
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value,
    JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
    public override bool CanConvert(Type objectType)
    {
        return objectType.GetTypeInfo().IsClass;
    }
    public override object ReadJson(JsonReader reader, Type objectType,
    object existingValue, JsonSerializer serializer)
    {
        object instance = Activator.CreateInstance(objectType);
        var props = objectType.GetTypeInfo().DeclaredProperties.ToList();

        JObject jo = JObject.Load(reader);
        foreach (JProperty jp in jo.Properties())
        {
            if (!_propertyMappings.TryGetValue(jp.Name, out var name))
                name = jp.Name;
            PropertyInfo prop = props.FirstOrDefault(pi =>
            pi.CanWrite &&
            pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
            prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType,
            serializer));
        }
        return instance;
    }
}

Usage

static void Main(string[] args)
{
    Dictionary<string, string> test = new Dictionary<string, string>
    {
        {"name", "error"},
        {"code", "errorCode"},
        {"description", "message"}
    };
    var apiError2 = JsonConvert.DeserializeObject<ApiError>(payload2, new ApiErrorConverter(test));
}

Anyway, I just want to convert json map to C# object. How can I pass the dictionary into the ApiErrorConverter without this error?

How can i map the json to C# object with newtosoft.json flexibly like I'm trying to do?

Upvotes: 2

Views: 15055

Answers (1)

cjmurph
cjmurph

Reputation: 869

The problem is not that your ApiErrorConverter has a constructor, but how you call the json property value to object method. Because you pass the serializer, it calls the ReadJson method again for the property. This fails on the second property (a string) as a string doesn't have a parameterless constructor. If you use the ToObject overload that just specifies type, it will work. Given the data sample supplied, you also need to check for null on the PropertyInfo before using it.

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    object instance = Activator.CreateInstance(objectType);
    var props = objectType.GetTypeInfo().DeclaredProperties.ToList();

    JObject jo = JObject.Load(reader);
    foreach (JProperty jp in jo.Properties())
    {
        if (!_propertyMappings.TryGetValue(jp.Name, out var name))
            name = jp.Name;
        PropertyInfo prop = props.FirstOrDefault(pi =>
        pi.CanWrite &&
        pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
        prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType));
    }
    return instance;
}

Answer to extra comments:

The question was asked, given a different json structure:

string payload2 = "{\"statusCode\":123,\"code\":\"error_code\",\"name\":\"hahaha\",\"description\":\"The Message\",\"qqqitem\":[{\"ahah\":\"cl\",\"ahahah\":\"item\"}]}";

How to get the array named qqqitem into my deserialised object?

Firstly, the array is an object with two properties, ahah and ahahah, we need to make a class for this. I'll call it Item.

public class Item
{
    public string ahah { get; set; }

    public string ahahah { get; set; }
}

Then we need our ApiError class to contain an array of these objects. I'll create it as a list.

public class ApiError
{
    [JsonProperty("error")]
    public string Error { get; set; }

    [JsonProperty("errorCode")]
    public string ErrorCode { get; set; }

    [JsonProperty("message")]
    public string Message { get; set; }

    [JsonProperty("statusCode")]
    public int StatusCode { get; set; }

    [JsonProperty("items")]
    public List<Item> Items { get; set; }
}

Finally, since the names dont match I'll add a mapping

Dictionary<string, string> test = new Dictionary<string, string>
{
    {"name", "error"},
    {"code", "errorCode"},
    {"description", "message"},
    {"qqqitem", "items"}
};

Upvotes: 2

Related Questions