I develop a small program which allows to serialize/deserialize a class in Json.
This program, I make it .NET 7 and therefore I use System.Text.Json
So I have an ITest interface. This interface is implemented in two classes TestSuite and TestCase.
[JsonDerivedType(typeof(TestSuite), "testSuite")]
[JsonDerivedType(typeof(TestCase), "testCase")]
[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
public interface ITest
{
}
public class TestCase : ITest
{
public string Name { get; set; }
}
public class TestSuite : ITest, IEnumerable<ITest>
{
private readonly List<ITest> _tests = new ();
public void Add(ITest test)
{
_tests.Add(test);
}
/// <inheritdoc />
public IEnumerator<ITest> GetEnumerator()
{
return _tests.GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
And to test, I do:
ITest suite = new TestSuite { new TestCase { Name = "Oui" }, new TestCase { Name = "Test2" } };
string json = JsonSerializer.Serialize(suite);
var s = JsonSerializer.Deserialize<ITest>(json);
Console.WriteLine(json);
TestCase serialization and deserialization works perfectly.
But for TestSuite deserialization fails with error message:
System.NotSupportedException: 'The collection type 'Test.TestSuite' is abstract, an interface, or is read only, and could not be instantiated and populated. Path: $.$values | LineNumber: 0 | BytePositionInLine: 32.'
I can't use custom JsonConverter because json polymorphism only supports json converter by default.
Do you know how I could solve this problem?
Thanks in advance,
I tried to create a custom JsonConverter for TestSuite but I can't. Then I tried to abort json polymorphism and create a custom JsonConverter for ITest but this is not a good idea.
The problem here is that your class implements a collection interface and ATM there is no build in way to tell System.Text.Json
to serialize your object as object, not as a collection (track this one for when it will become possible).
If you really-really need to use current class structure you can go down a rabbit hole of reflection and mess with System.Text.Json
internals (note that this can be very brittle):
// tells to serialize `TestSuite` as object
internal sealed class ObjectConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(TestSuite);
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var type = typeof(JsonConverterFactory).Assembly.GetType("System.Text.Json.Serialization.Converters.ObjectConverterFactory");
var f = (JsonConverterFactory)Activator.CreateInstance(type, new object[]{true});
return f.CreateConverter(typeToConvert, options);
}
}
// exposes private property to serializer
static void Test(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Type == typeof(TestSuite))
{
var field = typeof(TestSuite).GetField("_tests", BindingFlags.Instance | BindingFlags.NonPublic);
JsonPropertyInfo jsonPropertyInfo = jsonTypeInfo.CreateJsonPropertyInfo(field.FieldType, field.Name);
jsonPropertyInfo.Get = field.GetValue;
jsonPropertyInfo.Set = field.SetValue;
jsonTypeInfo.Properties.Add(jsonPropertyInfo);
}
}
And usage:
ITest suite = new TestSuite { new TestCase { Name = "Oui" }, new TestCase { Name = "Test2" } };
var jsonSerializerOptions = new JsonSerializerOptions
{
Converters = { new ObjectConverterFactory() },
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { Test }
}
};
string json1 = JsonSerializer.Serialize(suite, jsonSerializerOptions);
var s = JsonSerializer.Deserialize<ITest>(json1, jsonSerializerOptions);
Note again that this can be very brittle (mainly due to ObjectConverterFactory
implementation) and I would recommend to to remove the IEnumerable<ITest>
from TestSuite
.