Goodbye custom converters, hello clean polymorphic JSON in .NET 7+
#

Ever tried to split lots of different actions into inheritance-friendly classes and interfaces, then make them configurable through a connected frontend?

Sounds easy, right?
 Yeah… until you try to serialize and deserialize them with all those different generic parameters floating around. Then things get ugly. 😅


The First Attempts (a.k.a. “How to Make Your Life Harder”)
#

Our first approach was to just use dynamic.
 .NET: “Sure, I can do that… wait, no, I can’t make you a nice mixed list of completely unrelated types without breaking everything.”

Then we tried building custom converters.
They worked, though the approach was both time-consuming and prone to errors.


The Hero Arrives: JsonPolymorphic
#

Starting from .NET 7, System.Text.Json got a new superpower — polymorphic serialization and deserialization without having to roll your own converter factory from scratch.

Here’s the magic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(Configuration), "config")]
[JsonDerivedType(typeof(Configuration2), "config2")]
public interface IConfiguration { }

public class Configuration : IConfiguration
{
    public int Number { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

public class Configuration2 : IConfiguration
{
    public string Configuration { get; set; }
}

Serializing
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var obj = new List<IConfiguration>
{
    new Configuration { Number = 42, Name = "MainConfig", IsActive = true },
    new Configuration2 { Configuration = "Special settings" }
};

var options = new JsonSerializerOptions
{
    WriteIndented = true
};

var json = JsonSerializer.Serialize(obj, options);
Console.WriteLine(json);

The output now includes a $type discriminator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[
  {
    "$type": "config",
    "Number": 42,
    "Name": "MainConfig",
    "IsActive": true
  },
  {
    "$type": "config2",
    "Configuration": "Special settings"
  }
]

Why This Is Awesome 🚀
#

  • No more hand-crafted converters (and their inevitable bugs).
  • Works out-of-the-box with System.Text.Json.
  • Keeps your interfaces intact, so your architecture stays clean.

When deserializing, .NET will look at the $type and automatically pick the right implementation for you.


TL;DR 📝
#

If you’ve been fighting with serializing/deserializing interfaces or abstract classes in .NET and ended up in dynamic hell or converter purgatory,
 ➡️ try JsonPolymorphic and JsonDerivedType.
 Your future self will thank you. 🙌