Search code examples
c#.netjsonjsonconverter

How to deserialize an item into different types? (Newtonsoft, C#, .NET)


I'm getting a json message with a field called "item" that can be deserialized as class foo or class bar. How do I implement this in C#?

I assume something like this:

public class FooOrBar 
{
     // Only one of the two will get populated.
     public Foo foo { get; set; }
     public Bar bar { get; set; }
     bool is_foo;
}

[JsonProperty("item")]
[JsonConverter(typeof(CustomConverter))]
public FooOrBar Item { get; set; }

How do I implement the custom converter for such a case?

Is there a simpler solution?


Solution

  • I'm not sure how Foo or Bar is determined in your code, but for now let's assume you have a JSON structure like this:

    var json = @"
    {
      isFoo: true,
      item: {}
    }";
    

    In which case Foo or Bar is determined by isFoo.

    FooOrBar class could be subjective. I would not define class like that, but use polymorphism:

    interface IItem
    {
        bool IsFoo { get; }
    }
    
    class Foo : IItem
    {
        public bool IsFoo => true;
    }
    
    class Bar : IItem
    {
        public bool IsFoo => false;
    }
    
    [JsonConverter(typeof(CustomJsonConverter))]
    class MyClass
    {
        public IItem Item { get; set; }
    }
    

    I set CustomJsonConverter to MyClass because of the JSON structure I have. I need to use information available in the parent object of item to determine Foo or Bar so I have to set JsonConverter to the declaring type of Item property. If you use information available in item property, you can move converter to the Item property.

    Now implement CustomJsonConverter like this:

    class CustomJsonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType) => true;
    
        public override object ReadJson(JsonReader reader, Type objectType, 
            object existingValue, JsonSerializer serializer)
        {
            var token = JToken.ReadFrom(reader);
            if (token.Type == JTokenType.Null)
            {
                return null;
            }
    
            // Create an instance of MyClass, and set property as per "isFoo".
            var obj = new MyClass();        
    
            if (token["isFoo"].Value<bool>())
            {
                obj.Item = new Foo();
            }
            else
            {
                obj.Item = new Bar();
            }
    
            // Populate properties
            serializer.Populate(token.CreateReader(), obj);
            return obj;
        }
    
        public override void WriteJson(JsonWriter writer, object value, 
            JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }
    }