How to Deserialize JSON that Contains an Embedded JSON String in C#

By FoxLearn 3/19/2025 7:41:13 AM   40
In this article, I'll demonstrate how to handle deserializing JSON that contains an embedded JSON string, which I'll refer to as a "JSON package." This concept is like receiving a gift box (the JSON package) that contains a smaller gift (the embedded JSON data) inside it.

The outer box has labels and information (the metadata), while the inner gift has specific details about the item it represents.

While using an embedded JSON string is uncommon, there are situations where it may be necessary. If possible, consider deserializing to a derived type, using JsonDocument to parse the JSON string, or employing JsonExtensionData to catch properties that aren't part of the model. If you find that using an embedded JSON string is unavoidable, continue reading for an example.

Creating the JSON Package

Here’s an example of a JSON package, which contains an embedded JSON string (highlighted):

{
  "PackageType": "Order",
  "Details": "{\"OrderId\":\"12345\",\"Customer\":\"John Doe\",\"Total\":99.99}"
}

This JSON package contains routing information (PackageType) and an embedded JSON string (Details). The embedded JSON string is escaped (encoded), which is why it appears as a single string within the JSON package.

This JSON was generated by serializing the following class:

public class JsonPackage
{
    public string PackageType { get; set; }
    public string Details { get; set; }
}

The sender creates this JSON package by first serializing the order details, then wrapping it inside the JSON package:

var jsonPackage = new JsonPackage()
{
    PackageType = "Order",
    Details = JsonSerializer.Serialize(new Order()
    {
        OrderId = "12345",
        Customer = "John Doe",
        Total = 99.99
    })
};

var jsonToSend = JsonSerializer.Serialize(jsonPackage, options);

Routing the JSON Package

The sender sends the JSON package to a service. The service has a route map, which helps determine how to handle messages based on the PackageType field. Although routing can be set up in many ways, this article focuses on how to deserialize the JSON package.

To route the package correctly, the service first deserializes the JSON package to extract the routing information. Then it routes the embedded JSON string to the appropriate handler:

public static void Route(string json)
{
    var jsonPackage = JsonSerializer.Deserialize<JsonPackage>(json);

    var handler = routeMap[jsonPackage.PackageType];

    handler.Handle(jsonPackage.Details);
}

private static readonly Dictionary<string, IMessageHandler> routeMap = new Dictionary<string, IMessageHandler>();

This is an example of the plugin pattern, where plugins implement the IMessageHandler interface. The routing logic uses the value in the JSON package (PackageType) to look up the correct handler in the dictionary.

public interface IMessageHandler
{
    void Handle(string json);
}

Processing the Embedded JSON Data

Once the embedded JSON string is routed to the correct handler, it is deserialized into the appropriate object for further processing. In this example, the handler deserializes the embedded JSON into an Order object using a class called OrderProcessor:

public class OrderProcessor : IMessageHandler
{
    public void Handle(string orderJson)
    {
        var order = JsonSerializer.Deserialize<Order>(orderJson);

        Console.WriteLine($"Processing Order {order.OrderId} for {order.Customer}");
    }
}

public class Order
{
    public string OrderId { get; set; }
    public string Customer { get; set; }
    public decimal Total { get; set; }
}

The OrderProcessor class processes the embedded Order JSON by deserializing it and then performing any necessary actions (such as processing payment or updating inventory).