Reputation: 21
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:
string payload2 ="{\"statusCode\":123,\"name\":\"error_code\",\"description\":\"The Message\",\"item\":{\"ahah\":\"cl\"}}";
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; }
}
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;
}
}
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
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;
}
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