Deserializing JSON that contains an embedded JSON string

In this article, I’ll show an example of code that handles deserializing JSON that contains an embedded JSON string. I’ll refer to this as a JSON envelope. The reason to refer to this as an envelope is because it’s similar to how you send paper mail through the post office. You put a letter (the embedded JSON string) in an envelope that contains routing information (to / from address).

Needing to use an embedded JSON string is rather unusual. Consider deserializing to a derived type instead, using JsonDocument to parse the JSON string, or using JsonExtensionData to catch properties that aren’t part of the model. If you find that you really must use an embedded JSON string, read on.

Generating the JSON envelope

The following is an example of a JSON envelope, which contains an embedded JSON string (highlighted):

{
	"To": "PaymentProcessor",
	"Payload": "{\"Id\":\"1\",\"Amount\":20.21}"
}
Code language: JSON / JSON with Comments (json)

It contains routing information and an embedded JSON string. The only thing special about the embedded JSON is that it’s been escaped / encoded.

This JSON was generated by serializing the following class:

public class JsonEnvelope
{
	public string To { get; set; }
	public string Payload { get; set; }
}
Code language: C# (cs)

The sender would generate this JSON envelope by first serializing the payload, then the envelope, like this:

var jsonEnvelope = new JsonEnvelope()
{
	To = "PaymentProcessor",
	Payload = JsonSerializer.Serialize(new Payment()
	{
		Id = "1",
		Amount = 20.21m
	})
};

var jsonToSend = JsonSerializer.Serialize(jsonEnvelope, options);
Code language: C# (cs)

Routing the JSON envelope

The sender sends the JSON envelope to a service. The service contains a route map that provides information about how to route messages. You can populate the route map in many ways (that’s out of scope of this article though).

The service has to deserialize the JSON envelope to get the routing information. Once it has the routing information, it can route the embedded JSON string to the appropriate processor:

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

	var processor = routeMap[jsonEnvelope.To];

	processor.Process(jsonEnvelope.Payload);
}
private static readonly Dictionary<string, IMessageProcessor> routeMap = new Dictionary<string, IMessageProcessor>();
Code language: C# (cs)

This is using the plugin pattern, where plugins implement the IMessageProcessor interface (shown below). The routing code uses the string from the JSON message to lookup the value in the dictionary.

public interface IMessageProcessor
{
	void Process(string json);
}
Code language: C# (cs)

Processing the embedded JSON string

The embedded JSON string is passed into the processor. The JSON can then be deserialized into the appropriate type and processed. In this example, the JSON is being passed into a PaymentProcessor class, which deserializes the JSON into a Payment object.

public class PaymentProcessor : IMessageProcessor
{
	public void Process(string paymentJson)
	{
		var payment = JsonSerializer.Deserialize<Payment>(paymentJson);

		Console.WriteLine($"Processing payment {payment.Id}");
	}
}
public class Payment
{
	public string Id { get; set; }
	public decimal Amount { get; set; }
}
Code language: C# (cs)