Json.NET
C#
Deserialization
Interfaces
Object Mapping

Casting interfaces for deserialization in Json.NET

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Json.NET cannot instantiate an interface directly because interfaces are contracts, not concrete objects. If you deserialize into an interface type without concrete-type guidance, mapping fails. The practical solution is to supply type resolution explicitly through converters, metadata, or DTO mapping layers.

Why Interface Deserialization Fails by Default

Deserializer needs a concrete class with allocatable structure. Interface types such as IAnimal do not provide constructors or concrete member layout.

So this fails conceptually:

  • target type is IAnimal
  • payload describes one concrete subtype
  • serializer has no rule for which subtype to instantiate

This is expected behavior and should be handled through explicit mapping strategy.

Strategy 1: Discriminator-Based Custom Converter

The safest general approach is a custom converter using a discriminator field such as kind.

csharp
1using Newtonsoft.Json;
2using Newtonsoft.Json.Linq;
3
4public interface IAnimal
5{
6    string Name { get; set; }
7}
8
9public class Dog : IAnimal
10{
11    public string Name { get; set; } = "";
12    public int BarkLevel { get; set; }
13}
14
15public class Cat : IAnimal
16{
17    public string Name { get; set; } = "";
18    public int Lives { get; set; }
19}
20
21public class AnimalConverter : JsonConverter<IAnimal>
22{
23    public override IAnimal? ReadJson(JsonReader reader, Type objectType, IAnimal? existingValue, bool hasExistingValue, JsonSerializer serializer)
24    {
25        var obj = JObject.Load(reader);
26        var kind = obj["kind"]?.Value<string>();
27
28        return kind switch
29        {
30            "dog" => obj.ToObject<Dog>(serializer),
31            "cat" => obj.ToObject<Cat>(serializer),
32            _ => throw new JsonSerializationException($"Unsupported kind: {kind}")
33        };
34    }
35
36    public override void WriteJson(JsonWriter writer, IAnimal? value, JsonSerializer serializer)
37    {
38        serializer.Serialize(writer, value);
39    }
40}

This keeps allowed subtypes explicit and auditable.

Strategy 2: Metadata-Based Type Resolution

Json.NET also supports type metadata-based resolution. This can work in trusted internal systems but should be used carefully with untrusted payloads.

csharp
1using Newtonsoft.Json;
2
3var settings = new JsonSerializerSettings
4{
5    TypeNameHandling = TypeNameHandling.Auto
6};
7
8string json = JsonConvert.SerializeObject(new Dog { Name = "Rex", BarkLevel = 3 }, settings);
9var animal = JsonConvert.DeserializeObject<IAnimal>(json, settings);

Metadata strategy is convenient, but security and compatibility implications must be reviewed before broad use.

Strategy 3: DTO First, Interface Mapping Second

In many production systems, the cleanest design is to deserialize into concrete transport DTOs, then map into domain interfaces. This separates wire format concerns from domain abstraction.

Benefits:

  • no serializer-specific logic in core domain layer
  • easier API versioning
  • clearer testing of mapping rules

Example flow:

  1. deserialize JSON into AnimalDto.
  2. map AnimalDto to concrete domain object implementing IAnimal.
  3. return interface to business logic.

This pattern often reduces framework lock-in.

Contract Design Recommendations

If you support polymorphic payloads, make discriminator field mandatory and documented. Do not infer type from optional field presence.

Good contract rules:

  • one required discriminator key
  • clearly defined subtype schema per discriminator value
  • explicit behavior for unknown discriminator values

Unknown values should fail fast by default unless backward-compatibility policy says otherwise.

Testing Interface Deserialization

Add test coverage for:

  • each supported subtype
  • unknown discriminator handling
  • missing discriminator handling
  • round-trip serialization if required

Without these tests, subtype additions can silently break existing consumers.

For security-sensitive APIs, include tests that ensure unsupported type metadata is rejected.

Common Pitfalls

Deserializing directly to interface without converter strategy causes runtime mapping errors.

Using permissive type metadata with untrusted input can introduce security risk.

Spreading subtype mapping across many files makes polymorphic behavior hard to audit.

Assuming subtype inference from optional fields can break when payload evolves.

Summary

  • Interfaces cannot be instantiated directly during Json.NET deserialization.
  • Use explicit concrete-type resolution through converters or controlled metadata.
  • Discriminator-based converters are usually the clearest and safest option.
  • DTO-to-domain mapping can keep serializer concerns outside core abstractions.
  • Test subtype resolution paths and unknown-type behavior explicitly.

Course illustration
Course illustration

All Rights Reserved.