Home > OS >  When returning a list of an interface from an API controller, how to include the subclasses' pr
When returning a list of an interface from an API controller, how to include the subclasses' pr

Time:11-29

Situation

I'm writing an application that can display different types of charts, with a vanilla JS front-end and an ASP.NET 6 API back-end. There are different types of charts in the application, like LineChart and PieChart. These different chart types all implement the interface IChart. The IChart interface has properties for information like the ID and name of the chart, whereas the LineChart, for example, has properties for the name of the X- and Y-axis.

Here are the model classes as minimum example:

IChart.cs:

public interface IChart
{
    public int Id { set; get; }

    public string Title { set; get; }

    public string Description { set; get; }
}

LineChart.cs:

public class LineChart : IChart
{
    public int Id { get; set; }

    public string Title { get; set; }

    public string Description { get; set; }

    public string xAxisName { get; set; }

    public string yAxisName { get; set; }
}

My Problem

When I'm returning a single chart using an ASP.NET API-controller, it works just fine; if the object I return is a LineChart, I get the property xAxisName in the JSON response even if the API method return type is IChart. But when I return multiple Charts in a List<IChart>, I only get the properties provided by IChart in my JSON, so no xAxisName. How can I make a controller return all the items in the List<IChart while keeping the properties of the derived classes in the JSON result? Is there some JSON middleware setting I need to set?

As an illustration, here are a few sample API calls.

The following code returns a LineChart with the X axis name set in the JSON reponse body:

// POST: api/<ChartController>
    [HttpPost("{xAxisName}")]
    public IChart MakeOneChart(string xAxisName)
    {
        IChart chart = new LineChart() {xAxisName = xAxisName};

        return chart;
    }

But in the following code, the response body is not filled with values for the X axis

// GET: api/<ChartController>
    [HttpPost]
    public IEnumerable<IChart> MakeManyCharts()
    {
        IEnumerable<IChart> charts = new List<IChart>()
        {
            new LineChart() {xAxisName = "Duration (in minutes)"},
            new LineChart() {xAxisName = "Duration (in years)"}
        };

        return charts;
    }

CodePudding user response:

People here had a similar problem. The returned object has an interface as a property and the derived classes didn't serialize properly but I bet any property defined in that interface would show.

Now I don't know much about the inner workings of Json conversion but from what I've observed, here's what I'm guessing is happening:

The standard Json serializer provided with .Net doesn't recognize the object's runtime type so it doesn't know anything about the object beneath the surface. Only exception is the outermost object you're returning. For example, when your return type is IChart, it can recognize when you return LineChart or PieChart since it's just checking the outermost object. However, when you return IEnumerable<IChart> it can't recognize the derived type of IChart objects since they're wrapped by IEnumerable. I bet if you returned List<IChart> it could recognize properties related to List<T>


There are some possible solutions:

  • An answer in the linked question suggests using Json.NET package from Newtonsoft

  • A different answer from the question suggests writing custom serializers

  • Another option I would say would be to try writing a custom formatter. Check the type of object and cast it like this:

    if (context.Object is IEnumerable<LineChart> lineCharts)
    {
        // serialize the lineCharts
    }
    else if (context.Object is IEnumerable<PieChart> pieCharts)
    {
        // serialize the pieCharts
    }
    // return the json string
    

    though this type of solution probably would not work if you have base interfaces as properties like in the question and you wanted the derived objects of those to be recognized in the serialization (or rather you would have to write nested if statements and check every property of every chart object in the IEnumerable and it would go on and on if you have a very complex object), but should be enough for your situation

  • Related