0

Given the following json

{
  "filters": [
    { "filterType": "fooFilter", "min": 1, "max": 5, "size":3 },
    { "filterType": "barFilter", "multiplier": "1.953283", "average": "5.732837" },
    { "filterType": "bazFilter", "limit": 10, "createdBefore": "1641942136" }
  ]
}

I want to create an array of filter objects like so:

public class MyRootClass
{
    public IFilter[] Filters { get; init; } = default!;
}
public interface IFilter
{
    public string Name { get; }
}

public class FooFilter : IFilter
{
    public string Name => nameof(FooFilter);
    public int Min { get; init; }
    public int Max { get; init; }
    public int Size { get; init; }
}

public class BarFilter : IFilter
{
    public string Name => nameof(BarFilter);
    public decimal Multiplier { get; init; }
    public decimal Average { get; init; }
}

public class BazFilter : IFilter
{
    public string Name => nameof(BazFilter);
    public int Limit { get; init; }
    public long CreatedBefore { get; init; }
}

In the above example, I made Name as a auto get readonly property to show that the value from from the json does not need to be stored. I included several types in each 'IFilter' type that should have built in converters already (ie. createdBefore is a string in json (quoted), but the built in converters will handle conversion to long automatically).

Also to note, there will only ever be either 0 or 1 of each filter type in the array, so there will be no collisions with multiple filters that are the same type.

What I need help with here is writing a JsonConverter that will read the "filterType" in each element of the array in 'filters' and create instances of that filter.

Producing an output similar to this:

var filters = new List<IFilter> {
  new FooFilter { Min = 1, Max = 5, Size = 3 },
  new BarFilter { Multiplier = (decimal) 1.953283, Average = (decimal) 5.732837 },
  new BazFilter { Limit = (int) 10, CreatedBefore = (long) 1641942136 }
}

How can I build the converter to automatically convert each of the types based on the 'filterType' value inside the individual array. I am thinking along the lines of a switch / case inside of the converter for simplicity, but bonus gold stars for using reflection (in this case I have a pretty short list of Filter types, (less than 10), however down the road, I will be adding a plugin system to (other parts of) my application that could benefit from dynamic conversion using reflection, but I am pretty solid in reflection stuff, so I can do that later if you could explain how to just write the converter portion for this.

Also I am specifically using System.Text.Json libraries as the rest of my application uses that and I don't want to intermix other libraries such as NewtonSoft or Macross.Json.Extensions. I am using the latest .NET6 (.NET Core) and C#10.

Edit:

As I may not have been clear, (I believe this needs to be done with a JsonConverter) so it is converted automatically as this is how I am deserializing:

using (var client = new HttpClient());

var response = await client.GetAsync(url);

if (response.IsSuccessStatusCode)
{
  var myRootClass = await response.Content.ReadFromJsonAsync<MyRootClass>();
}
else
{
  // Handle errors accordingly
}

so the ReadFromJsonAsync<T> method is what I am using to deserialize the json

Aaron Murray
  • 1,920
  • 3
  • 22
  • 38
  • 2
    You can preload into a `JsonDocument`, check the inner field type discriminator, then deserialize to the final type as shown in [this answer](https://stackoverflow.com/a/59785679/3744182) to [Is polymorphic deserialization possible in System.Text.Json?](https://stackoverflow.com/q/58074304/3744182). Other answers may apply also. In fact that looks like a duplicate, agree? – dbc Jan 12 '22 at 00:27
  • @dbc, that SO actually looks very close to what I was looking for (guess I didn't use the right search terminology when looking for existing solutions). Will test and confirm. – Aaron Murray Jan 12 '22 at 01:20
  • 1
    @dbc yep, that was exactly what I was looking for, thank you! I got it working with that (had to do a bit of refactoring to account for lower case names and such, but it does in fact work, with one caveat: if I pass the converter in the options (as shown in the SO answer) it works, however if I try to add the converter as an attribute to the Filters property in the base class I get the exception `The converter specified on 'TestDeserialize.MyRootClass.Filters' is not compatible with the type 'System.Collections.Generic.IEnumerable \`1[TestDeserialize.IFilter]'. – Aaron Murray Jan 12 '22 at 02:42
  • @dbc with that said, I can work with that caveat, more than happy to have this question closed as duplicate to the SO provided. – Aaron Murray Jan 12 '22 at 02:44

0 Answers0