0

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.

green
  • 153
  • 3
  • 13
  • I'm not entirely sure I understand the problem. As long as `KnownItemComponents.TryGetKnownComponentType(itemComponentName, out Type itemComponentType)` is returning the final, concrete type, why is any sort of converter nesting required? Can you please [edit] your question to share a [mcve]? – dbc Nov 01 '19 at 15:52
  • I added two example object I am trying to deserialize. The problem is not finding the type. The type is resolved correctly. The problem is when you have a nested object with subcomponents of the same interface type. – green Nov 04 '19 at 09:05
  • So, the problem is that you've added the converter `[JsonConverter(typeof(ItemComponentConverter))]` to the property `public IItemComponent ItemComponent { get; internal set; }` -- but not to other properties of type `IItemComponent`? Why not just add the converter to the other properties? – dbc Nov 04 '19 at 16:56
  • Or register the converter globally e.g. as shown in [Registering a custom JsonConverter globally in Json.Net](https://stackoverflow.com/q/19510532). (The way to do that depends on your version of asp.net.) – dbc Nov 04 '19 at 16:58
  • I cannot use ```JsonConvert.SerializeObject``` because the serialization happens behind the scenes with ```[FromBody] Item item```, using the converter given as attribute. Because I have an item as parameter, by the time I can access the object it is too late because it already tries deserializing it. So globally setting it would not work in this case, also anything that uses the the object itself as parameter like ```JsonConvert.SerializeObject```. For the latter I would need to have [FromBody] string/JObject Item, which I don't want to have. But anyway, thank you for the input! – green Nov 05 '19 at 08:45

0 Answers0