Implementing Custom JsonConverter in JSON.NET for Polymorphic Deserialization

Nov 20, 2025 · Programming · 12 views · 7.8

Keywords: JSON.NET | Custom Converter | Polymorphic Deserialization | JsonConverter | .NET Serialization

Abstract: This article provides an in-depth exploration of implementing custom JsonConverter in JSON.NET to handle polymorphic deserialization scenarios. Through detailed code analysis, it demonstrates how to create an abstract base class JsonCreationConverter<T> inheriting from JsonConverter and implement its key methods. The article focuses on explaining the implementation logic of the ReadJson method, including how to determine specific types by analyzing JSON fields through JObject, and how to correctly copy JsonReader configurations to ensure deserialization accuracy. Additionally, the article compares different implementation approaches and provides complete code examples with best practice recommendations.

Introduction

In modern software development, JSON serialization and deserialization are extremely common requirements. When dealing with inheritance hierarchies, polymorphic deserialization becomes a challenging problem. JSON.NET, as a widely used JSON processing library in the .NET ecosystem, provides powerful custom converter mechanisms to address such issues.

Problem Context

Consider the following class hierarchy:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Employee : Person
{
    public string Department { get; set; }
    public string JobTitle { get; set; }
}

public class Artist : Person
{
    public string Skill { get; set; }
}

When we need to deserialize a JSON array containing mixed type objects, the standard deserialization mechanism cannot automatically identify specific types:

[
  {
    "Department": "Department1",
    "JobTitle": "JobTitle1",
    "FirstName": "FirstName1",
    "LastName": "LastName1"
  },
  {
    "Department": "Department2",
    "JobTitle": "JobTitle2",
    "FirstName": "FirstName2",
    "LastName": "LastName2"
  },
  {
    "Skill": "Painter",
    "FirstName": "FirstName3",
    "LastName": "LastName3"
  }
]

Core Solution

Abstract Base Class Design

Create a generic JsonCreationConverter<T> abstract base class to provide a framework for polymorphic deserialization:

public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, 
                                    Type objectType, 
                                    object existingValue, 
                                    JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
            
        JObject jObject = JObject.Load(reader);
        T target = Create(objectType, jObject);
        
        using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
        {
            serializer.Populate(jObjectReader, target);
        }
        
        return target;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

Reader Configuration Copying

To ensure deserialization consistency, copy the original JsonReader configuration:

public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
{
    JsonReader jTokenReader = jToken.CreateReader();
    jTokenReader.Culture = reader.Culture;
    jTokenReader.DateFormatString = reader.DateFormatString;
    jTokenReader.DateParseHandling = reader.DateParseHandling;
    jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
    jTokenReader.FloatParseHandling = reader.FloatParseHandling;
    jTokenReader.MaxDepth = reader.MaxDepth;
    jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
    return jTokenReader;
}

Specific Converter Implementation

Implementation of the specific converter for the Person class hierarchy:

public class PersonConverter : JsonCreationConverter<Person>
{
    protected override Person Create(Type objectType, JObject jObject)
    {
        if (FieldExists("Skill", jObject))
        {
            return new Artist();
        }
        else if (FieldExists("Department", jObject))
        {
            return new Employee();
        }
        else
        {
            return new Person();
        }
    }

    private bool FieldExists(string fieldName, JObject jObject)
    {
        return jObject[fieldName] != null;
    }
}

Implementation Details Analysis

Type Inference Strategy

The converter determines specific types by checking the presence of particular fields in the JSON object:

Deserialization Process

  1. Load JObject from JsonReader
  2. Call Create method to create specific type instance based on field analysis
  3. Populate object properties using properly configured JsonReader
  4. Return fully deserialized object

Usage Example

string json = "[...]"; // JSON string
List<Person> persons = JsonConvert.DeserializeObject<List<Person>>(json, new PersonConverter());

Best Practices

Error Handling

When implementing custom converters, fully consider edge cases and error handling:

Performance Considerations

To optimize performance:

Comparison with Alternative Approaches

Compared to solutions using TypeNameHandling, custom converters provide:

Conclusion

By implementing custom JsonConverter, developers can flexibly handle complex polymorphic deserialization scenarios. The JsonCreationConverter<T> pattern provided in this article offers a reusable solution for such problems while ensuring configuration consistency and code maintainability. In practical applications, developers can adjust type inference logic and error handling strategies according to specific requirements.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.