I'm trying to do a polymorphic deserialization using System.Text.Json in net-8.0. Here is my schema.

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(Manager), "Manager")]
[JsonDerivedType(typeof(Developer), "Developer")]
public class Employee
{
    public string Type { get; set; }
    public string Name { get; set; }
}

public class Manager : Employee
{
    public string Phone { get; set; }
}

public class Developer : Employee
{
    public string Laptop { get; set; }
}

Here is my json.

[
  {
    "type": "Manager",
    "name": "Bob",
    "phone": "9876543210"
  },
  {
    "type": "Developer",
    "name": "Charlie",
    "laptop": "MacBook Pro"
  }
]

and I'm doing this

var jsonFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Employees.json");
var employeesContent = File.ReadAllText(jsonFilePath);

var jsonSerializerOptions = new JsonSerializerOptions();
jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
var employees = JsonSerializer.Deserialize<List<Employee>>(employeesContent, jsonSerializerOptions);

And the Type property inside Employee class is always null as shown in below screen shot.

Debug output

Please assist me on what I'm missing.

2 Replies 2

The type discriminator can't be an entity property as well. That's why the default is $type, not type. Right now you have an attribute named type that, according to the CamelCase options maps to the property Type and a discriminator also called type, that would map to the same attribute.

If you tried to serialize an array of Employees

Employee[] x=[new Manager{Name="Bob",Phone="9876543210"}, new Developer{Name="Charlie",Laptop="Dell"}];

JsonSerializer.Serialize(x, jsonSerializerOptions);

you'd get an exception :

The type 'Submission#5+Manager' contains property 'type' that conflicts with an    │
│ existing metadata property name. Consider either renaming it or ignoring it with JsonIgnoreAttribute.

If you didn't use camel case, you'd get :

[                                                                                                                    
 {"type":"Manager","Phone":"9876543210","Type":null,"Name":"Bob"},
 {"type":"Developer","Laptop":"Dell","Type":null,"Name":"Charlie"}
] 

That can be deserialized if you don't use camel case.

The best choice it to not rename the discriminator, or ensure it doesn't start with $ so it can't be confused with actual types.

If you insist on using type with camel case, don't use it as a property in any entity. :

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(Manager), "Manager")]
[JsonDerivedType(typeof(Developer), "Developer")]
public class Employee
{
    public string Name { get; set; }
}

public class Manager : Employee
{
    public string Phone { get; set; }
}

public class Developer : Employee
{
    public string Laptop { get; set; }
}

In that case this :

var xx=JsonSerializer.Deserialize<Employee[]>(json,jsonSerializerOptions);

Produces

│ Employee[]                                                                                                           │
│       - Phone: 9876543210                                                                                            │
│         Name: Bob                                                                                                    │
│       - Laptop: MacBook Pro                                                                                          │
│         Name: Charlie   

If you want (or need to) keep Type as a property and write it to JSON as well as read it from JSON and have it be your discriminator, then you'll need to implement a custom JsonConverter for that, something like this

public class EmployeeConverter : JsonConverter<Employee>
{
    public override Employee Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;

        // Note the lower case 't', because your JSON contains 'type' and not 'Type'
        var type = root.GetProperty("type").GetString();
        Employee employee = type switch
        {
            "Manager" => JsonSerializer.Deserialize<Manager>(root.GetRawText(), options),
            "Developer" => JsonSerializer.Deserialize<Developer>(root.GetRawText(), options),
            // Alternatively throw an Exception
            _ => JsonSerializer.Deserialize<Employee>(root.GetRawText(), options)
        };

        return employee!;
    }

    public override void Write(Utf8JsonWriter writer, Employee value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, value, value.GetType(), options);
    }
}

You can check out a sample on dotnetfiddle here

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.