I have a custom JsonConverter which I need for serialization in my controller( [FromBody] ). The problem is that when I have nested interfaces on this property, the serializer is recreated therefore the converter setting is lost.
public class ItemController
{
public async Task<IActionResult> PostItem([FromBody] Item item)
{
var itemDocument = await this.itemRepository.CreateItemAsync(item);
return Ok(itemDocument);
}
}
public class Item
{
[JsonProperty("itemComponent", NullValueHandling = NullValueHandling.Ignore)]
**[JsonConverter(typeof(ItemComponentConverter))]**
public IItemComponent ItemComponent { get; internal set; }
}
public class ItemComponentConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(IItemComponent);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject parameters = JToken.ReadFrom(reader) as JObject;
if (parameters?["$type"] == null)
{
return null;
}
string itemComponentName = parameters["$type"].Value<string>();
if (!KnownItemComponents.TryGetKnownComponentType(itemComponentName, out Type itemComponentType))
{
return null;
}
return parameters.ToObject(itemComponentType, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException("Cannot write from " + nameof(ItemComponentConverter));
}
}
My solution so far is adding the serializer as a field in my converter where I create it with the correct serializationSetting:
public class ItemComponentConverter : JsonConverter
{
private static JsonSerializer mySerializer = JsonSerializer.Create(new JsonSerializerSettings
{
Converters = { new ItemComponentConverter(), },
});
...
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
...
return parameters.ToObject(itemComponentType, **mySerializer**);
}
...
}
It works, but I would like a better solution if that is an option. As far as my search goes, the only other option was to iterate through all the object properties and have a switch which is creating the instance of the object. This solution is not what I am looking for. Is there a way to set or change the serializer?
EDIT:
This is an example of object that works with the first implementation as well:
{
"key": "612f8544-8489-4571-86b4-118aedb95575",
"name": "TestItem",
"component": {
"ContextKey": "TestKey1",
"Lhs": "1234",
"$type": "and"
}
}
This is an example where the first implementation doesn't work. This is what I mean by nested interfaces:
{
"key": "612f8544-8489-4571-86b4-118aedb95575",
"name": "TestItem",
"component": {
"Left": {
"ContextKey": "TestKey1",
"Lhs": "1234",
"$type": "equals"
},
"Right": {
"ContextKey": "TestKey2",
"Collection": ["joe", "mark"],
"$type": "in"
},
"$type": "and"
}
}
What happens with the second example is that when the serializer tries to resolve the type "and", it will call a second time and then a third time the method ReadJson to deserialize the types "equals" and "in" that are part of the same object. All these three types are IItemComponent. The second implementation is addressing this problem by using the static serializer instead of the one in the parameters of ReadJson.