I'm currently in the process of migrating vom Newtonsoft to System.Text.Json. Newtonsoft was able to automatically deserialize objects, that have one or more Interface properties. With System.Text.Json I get the following error message for the respective classes when I try to accomplish the same:
each parameter in the deserialization constructor must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object.
I am able to avoid this problem by writing custom converters, but this would result in a lot of overhead for objects with multiple nested layers, where each Interface property can again have multiple Interface properties on their own (thus requiring multiple custom converters). Is there a simpler solution for that problem?
I create an example to illustrate the problem:
[Fact]
public void JsonTest()
{
var child = new Child(new Name("Peter"), 10);
var parent = new Parent(child);
var str = JsonSerializer.Serialize(parent);
var jsonObj = JsonSerializer.Deserialize<Parent>(str);
Console.WriteLine(jsonObj!.Child.Name);
}
}
[JsonConverter(typeof(ParentConverter))]
public class Parent
{
public Parent(Child child)
{
Child = child;
}
public IChild Child { get; set;}
}
[JsonConverter(typeof(ChildConverter))]
public class Child : IChild
{
[JsonConstructor]
public Child(Name name, int age)
{
Name = name;
Age = age;
}
public IName Name { get; set; }
public int Age { get; set; }
}
public class Name : IName
{
public string NameValue { get; set; }
public Name(string nameValue)
{
NameValue = nameValue;
}
}
public interface IChild
{
IName Name { get; }
int Age { get; }
}
public interface IName
{
string NameValue { get; }
}
public class ParentConverter : JsonConverter<Parent>
{
public override Parent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument document = JsonDocument.ParseValue(ref reader))
{
JsonElement root = document.RootElement;
if (root.TryGetProperty("Child", out JsonElement childElement))
{
Child? child = JsonSerializer.Deserialize<Child>(childElement.GetRawText(), options);
return new Parent(child!);
}
}
throw new JsonException("Invalid JSON data");
}
public override void Write(Utf8JsonWriter writer, Parent value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName("Child");
JsonSerializer.Serialize(writer, value.Child, options);
writer.WriteEndObject();
}
}
public class ChildConverter : JsonConverter<Child>
{
public override Child Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument document = JsonDocument.ParseValue(ref reader))
{
var name = new Name("Alex");
var age = 20;
JsonElement root = document.RootElement;
if (root.TryGetProperty("Name", out JsonElement nameElement))
{
name = JsonSerializer.Deserialize<Name>(nameElement.GetRawText(), options);
}
if (root.TryGetProperty("Age", out JsonElement ageElement))
{
age = JsonSerializer.Deserialize<int>(ageElement.GetRawText(), options);
}
return new Child(name!, age);
}
throw new JsonException("Invalid JSON data");
}
public override void Write(Utf8JsonWriter writer, Child value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName("Name");
JsonSerializer.Serialize(writer, value.Name, options);
writer.WritePropertyName("Age");
JsonSerializer.Serialize(writer, value.Age, options);
writer.WriteEndObject();
}
}
With these custom converters I get the intended behaviour. But is there a way to avoid writing a custom converter for each such classes?