Deserialize JSON to a derived type in C#
By FoxLearn 2/5/2025 8:43:12 AM 164
However, System.Text.Json
does not provide built-in support for this feature.
In this article, we will walk through how to deserialize JSON into a known derived type with System.Text.Json
and also show an alternative using Newtonsoft
with a helper library for whitelisting types.
Add a type name property to the base class
First, let's define an abstract base class and add a property to specify the type. We’ll call this property Category
and use a string to store the type name.
public abstract class Animal { public string Name { get; set; } public abstract string Category { get; } } public class Dog : Animal { public override string Category { get; } = nameof(Dog); public string Breed { get; set; } public bool IsTrained { get; set; } }
Serialize a derived type
Now, let's serialize an instance of the Dog
class. We’ll cast it to the base class Animal
so that the serializer will serialize the Category
property as well.
using System.Text.Json; Animal animal = new Dog() { Name = "Rex", Breed = "German Shepherd", IsTrained = true }; var json = JsonSerializer.Serialize((object)animal, new JsonSerializerOptions() { WriteIndented = true }); Console.WriteLine(json);
The output will look like this:
{ "Category": "Dog", "Breed": "German Shepherd", "IsTrained": true, "Name": "Rex" }
Deserialize to a dervived type
Now, to deserialize the JSON string back into the correct derived type, we can parse the Category
property and match it with known derived types. If the Category
matches Dog
, we deserialize to the Dog
class.
using System.Text.Json; Animal animal; using (var jsonDoc = JsonDocument.Parse(json)) { switch (jsonDoc.RootElement.GetProperty("Category").GetString()) { case nameof(Dog): animal = jsonDoc.RootElement.Deserialize<Dog>(); break; default: throw new JsonException("'Category' didn't match known derived types"); } } Console.WriteLine($"Deserialized to type {animal.GetType()}");
The output will show that it successfully deserialized to a Dog
object:
Deserialized to type Dog
Custom converter with derived type name
You can also create a custom converter to handle deserialization of types derived from the Animal
class. Below is a simple implementation that checks the Category
and deserializes accordingly.
using System.Text.Json; using System.Text.Json.Serialization; public class AnimalConverter : JsonConverter<Animal> { public override bool CanConvert(Type typeToConvert) { return typeof(Animal).IsAssignableFrom(typeToConvert); } public override Animal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using (var jsonDoc = JsonDocument.ParseValue(ref reader)) { switch (jsonDoc.RootElement.GetProperty("Category").GetString()) { case nameof(Dog): return jsonDoc.RootElement.Deserialize<Dog>(options); default: throw new JsonException("'Category' doesn't match a known derived type"); } } } public override void Write(Utf8JsonWriter writer, Animal animal, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, (object)animal, options); } }
Then, apply the converter to the base class Animal
:
using System.Text.Json.Serialization; [JsonConverter(typeof(AnimalConverter))] public abstract class Animal { public string Name { get; set; } public abstract string Category { get; } }
Now you can serialize and deserialize a Dog
object like this:
using System.Text.Json; Animal animal = new Dog() { Name = "Bella", Breed = "Golden Retriever", IsTrained = false }; var options = new JsonSerializerOptions() { WriteIndented = true }; var json = JsonSerializer.Serialize(animal, options); Console.WriteLine(json);
This outputs:
{ "Category": "Dog", "Breed": "Golden Retriever", "IsTrained": false, "Name": "Bella" }
To deserialize:
var dog = JsonSerializer.Deserialize<Animal>(json, options) as Dog; Console.WriteLine(dog.Breed);
The output confirms it successfully deserialized to a Dog
object:
Golden Retriever
Derived type deserialization with Newtonsoft and JsonSubTypes
Newtonsoft provides a built-in mechanism for deserializing derived types through the TypeNameHandling
setting, but this approach poses security risks. The built-in ISerializationBinder
method for whitelisting types can also be somewhat unwieldy.
A better solution is to use a helper library like JsonSubTypes. This library offers custom converters and attributes that integrate seamlessly with Newtonsoft, providing more secure and efficient ways to whitelist derived types.
First, you'll need to install both Newtonsoft.Json and JsonSubTypes via NuGet. You can do this through the Package Manager Console:
Install-Package Newtonsoft.Json Install-Package JsonSubTypes
1. Define Base and Derived Classes
Let's define an abstract base class Vehicle
and two derived classes: Car
and Bike
. We will also use the JsonSubtypes
library to specify how to deserialize these types based on a property, in this case, Category
.
using JsonSubTypes; using Newtonsoft.Json; [JsonConverter(typeof(JsonSubtypes), "Category")] [JsonSubtypes.KnownSubType(typeof(Car), "Car")] [JsonSubtypes.KnownSubType(typeof(Bike), "Bike")] public abstract class Vehicle { public string Make { get; set; } public abstract string Category { get; } } public class Car : Vehicle { public override string Category { get; } = "Car"; public int Wheels { get; set; } public bool IsElectric { get; set; } } public class Bike : Vehicle { public override string Category { get; } = "Bike"; public bool HasBasket { get; set; } }
In this example:
Vehicle
is the abstract base class.Car
andBike
are derived types that provide their own implementations of theCategory
property.
2. Serialize a Derived Type
Now, let’s create an instance of a Car
, serialize it to JSON, and observe how the Category
property is included.
using Newtonsoft.Json; Vehicle vehicle = new Car() { Make = "Tesla", Wheels = 4, IsElectric = true }; var json = JsonConvert.SerializeObject(vehicle, Formatting.Indented); Console.WriteLine(json);
The output will look like this:
{ "Category": "Car", "Make": "Tesla", "Wheels": 4, "IsElectric": true }
3. Deserialize into the Correct Derived Type
Now, let’s deserialize the JSON back into the correct derived type using the JsonConvert.DeserializeObject
method. JsonSubTypes will match the Category
property and deserialize it into the appropriate Car
or Bike
class.
var deserializedVehicle = JsonConvert.DeserializeObject<Vehicle>(json); if (deserializedVehicle is Car car) { Console.WriteLine($"Deserialized to a Car with {car.Wheels} wheels, electric: {car.IsElectric}"); }
This will output:
Deserialized to a Car with 4 wheels, electric: True
By using JsonSubTypes, we can easily handle derived type deserialization in a secure and efficient manner. Instead of relying on the less secure and more cumbersome TypeNameHandling
or ISerializationBinder
, JsonSubTypes allows us to define a clean and manageable way to deserialize into derived types based on a simple Category
property.
- Deserialize JSON using different property names in C#
- Deserialize JSON to a dictionary in C#
- Deserialize a JSON array to a list in C#
- Serialize a tuple to JSON in C#
- Serialize and Deserialize a Multidimensional Array in JSON using C#
- Modifying Date Format for JSON Serialization in C#
- Serialize anonymous types with System.Text.Json in C#
- Serialize to JSON in Alphabetical Order in C#