Home > Software design >  How to Deserialize different types of derived classes?
How to Deserialize different types of derived classes?

Time:12-11

I am trying to deserialize 2 different types of List including their derived classes

To explain it better I made the following example

Consider I have 2 systems:
Api System

  public class ItemController : ControllerBase
    {
        private readonly ILogger<ItemController> _logger;

        public ItemController(ILogger<ItemController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public async Task<ActionResult<BaseItem>> Get()
        {
            var items = new List<BaseItem>();
            items.Add(new BaseItem { Id = 1 });
            items.Add(new Item { Id = 2, ItemName = "some name", ItemType = "some type" });

            return Ok(items);
        }
    }

With the following Entities:

namespace SomeService.API.Entities
{
    public class BaseItem
    {
        public int Id { get; set; }
    }
    public class Item: BaseItem
    {
        public string ItemType { get; set; }
        public string ItemName { get; set; }
    }
}

App System

static async Task Main(string[] args)
        {
            var client = new HttpClient();
            var response = await client.GetAsync($"http://localhost:5000/Item");
            Thread.Sleep(3000);
            var dataAsString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            JsonSerializerSettings settings = new JsonSerializerSettings();
            var obj = JsonConvert.DeserializeObject<List<BaseItem>>(dataAsString);

        }  

With the following DTO

namespace SomeSystem.APP.DTOs
{
    public class BaseItem
    {
        public int Id { get; set; }
    }
    public class Item : BaseItem
    {
        public string ItemType { get; set; }
        public string ItemName { get; set; }
    }
}

Using this example the API system returns a List<BaseItem>, the response also includes the derived class Item properties

enter image description here

Problem appear when I'm deserializing it,
After deserialize App DTO List<BaseItem> it's not including the derived class properties. thus can't use var myItem = someBaseItem as Item and it returns null;
Reading this 2 threads Thread 1, Thread2 asking about a similar thing advising using: JsonSerializerSettings settings = new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All}; isn't helping in my case since SomeSystem.APP.DTOs.Item and SomeService.API.Entities.Item aren't the same types, both are different dlls.

1 workaround I was thinking about is making a class library and move all the entities there, then reference both systems to this class library, thus I will have the same types of entities in both systems, But, I do not want to use this work around because I don't want this 3rd party dependency between the 2 systems, I want them both completely independent.
So my question is,
Is there any other work around which I can deserialize that json at the App environment in a way I will be able to use the derived class properties later?

CodePudding user response:

You should work with Item. I think your confussion is using var since you are assuming is anonymous or variant type but is not. You are not returning one or another depending on the case.

You write:

 var items = new List<BaseItem>();
            items.Add(new BaseItem { Id = 1 });
            items.Add(new Item { Id = 2, ItemName = "some name", ItemType = "some type" });

but in compilation this code REALLY MEANS:

 List<Item>  items = new List<Item>();
            items.Add(new Item { Id = 1, ItemName = null, ItemType = null});
            items.Add(new Item { Id = 2, ItemName = "some name", ItemType = "some type" });

As architecture suggestion, you should not mix classes for response the same service, or you should use the real that you will return.

IF what you want is a variant type (I wouldn't suggest), you should read the question that @Eric comment. But dynamic or object is not var

CodePudding user response:

I don' t know why do you want to mix two classes in one list, it would be easier to include two lists in one view model, but if I needed it , I would create code like this

add json attributes to Item class

public class Item : BaseItem
{
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string ItemType { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string ItemName { get; set; }
}

code

var json = await response.Content.ReadAsStringAsync();

List<BaseItem> items =  JsonConvert.DeserializeObject<List<Item>>(json).Select(i => (BaseItem)i ).ToList();

It was tested in Visual Studio, output

    Id  ItemType    ItemName

    1   null        null
    2   some type   some name

or json format

[
  {
    "Id": 1
  },
  {
    "ItemType": "some type",
    "ItemName": "some name",
    "Id": 2
  }
]

but you can get the same result this way too

List<BaseItem> itemsOriginal = JArray.Parse(json).Select(item => (BaseItem) item.ToObject<Item>() ).ToList();

but if keeping a BaseItem instance is very important for you , you can create ViewModel

public class ItemViewModel : Item
{
    public string TypeName { get; set; }

    public ItemViewModel(string typeName)
    {
        TypeName = typeName;
    }
}

and get action

[HttpGet]
public async Task<ActionResult<List<ItemViewModel>> Get()
{
    var items = new List<ItemViewModel>();
    items.Add(new ItemViewModel("BaseItem") { Id = 1 });
    items.Add(new ItemViewModel("Item") { Id = 2, ItemName = "some name", ItemType = "some type" });

   return Ok(items);
}

list will be the same as you want

List<BaseItem> itemsOriginal = JArray.Parse(json)
.Select( item => item["TypeName"].ToString() == "BaseItem" ?
 item.ToObject<BaseItem>() : item.ToObject<Item>()).ToList();

But I don't see much practical use of this.

  • Related