I have some classes that model the structure I expect from my JSON file. For values that are an array in the JSON, the corresponding C# properties are declared to have the type IList<T> for various types T. I do not control these classes. When deserializing the JSON into the root type MyClass, how can I make it so that all properties of the classes that I get back of type IList<T> are constructed using the concrete type ObservableCollection<T>? Please note that MyClass has deeply nested properties of type IList<T>.

Hopefully the following example shows my intentions a bit more clearly:

class MyClass{
    public IList<NestedClass> MyList { get; set; }
}

class NestedClass{
    public IList<AnotherNestedClass> NestedList { get; set; }
}

class AnotherNestedClass{
    // ... various properties.
}
var obj = JsonSerializer.Deserialize<MyClass>(jsonString);
Console.WriteLine(obj?.MyList?.GetType()); // This should be ObservableCollection<NestedClass>
1

There are 1 best solutions below

1
dbc On BEST ANSWER

You can use a custom JsonConverterFactory to automatically deserialize any properties or values declared as IList<T> to be ObservableCollection<T>.

First define the following converter factory:

public class IListAsObservableCollectionConverter : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert) => 
        typeToConvert.IsInterface && typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(IList<>);
    
    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => 
        (JsonConverter)Activator.CreateInstance(typeof(IListAsObservableCollectionConverterGeneric<>).MakeGenericType(typeToConvert.GetGenericArguments()))!;
    
    class IListAsObservableCollectionConverterGeneric<T> : JsonConverter<IList<T>>
    {
        public override void Write(Utf8JsonWriter writer, IList<T> value, JsonSerializerOptions options) =>
            JsonSerializer.Serialize(writer, value, value.GetType(), options);
        public override IList<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
            JsonSerializer.Deserialize<ObservableCollection<T>>(ref reader, options);
    }
}

Then deserialize using the following options:

var options = new JsonSerializerOptions
{
    Converters = { new IListAsObservableCollectionConverter() },
    // Other options as required
    IncludeFields = true, // Required to serialize & deserialize public fields as well as properties.
};
var obj = JsonSerializer.Deserialize<MyClass>(json, options);

Demo fiddle here.