Make a deep copy of a C# object instance with JSON serialization
Photo by Ussama Azam on Unsplash

Make a deep copy of a C# object instance with JSON serialization

in

Introduction

In C#, there are reference types and value types. Value types are variables assigned to data that represent an object’s unique instance. With reference types, the variable points to a reference of an object. In this case, the variable is only a representation of the object. This means that if you modify a variable that points to a reference type, you will modify the value that the variable is pointing to. In this post, I will demonstrate this concept using an example of a Pizza order. I will then show you how to make a deep copy of a reference type using JSON serialization.

A Demonstration of Reference Types

Let’s define some classes (reference types) for our pizza order.

public class Pizza
{
   public string Size {get; set;}
   public List<string> Toppings {get; set;}
   public bool ExtraCheese {get; set;}
}

Now, we can use these classes to create our Pizza order

var pizzaOne = new Pizza()
 {
    Size = "XL",
    Toppings = new List<string>{"pepporoni", "sausage", "mushroom"},
    ExtraCheese = true,
 }

var pizzaTwo = new Pizza()
 {
    Size = "XL",
    Toppings = new List<string>{"ham", "sausage", "bacon"},
    ExtraCheese = true
 }

var order = new List<Pizza>()
{
    pizzaOne,
    pizzaTwo
};

I just heard that more friends are coming to our Super Bowl party. I need to order another XL Pizza with pepperoni, sausage, mushroom, and extra cheese (pizza one).

So, the pizza shop updated my order, and the following code was executed to make the change.

var pizzaThree = pizzaOne;

Since I ordered the same pizza, the program executes code that sets a pizzaThree variable to pizzaOne. pizzaThree is not a unique pizza order. It is a reference to the pizzaOne. We can use Object.Equals() to confirm the two object instances are equal.

Console.WriteLine(Object.Equals(pizzaOne, pizzaThree)); // True

You may be wondering why this is a problem. I did order the same pizza. It should be the same in the context of the program. The program needs to recognize my new pizza as a new object instance. Why? Suppose I changed my mind and called the pizza shop to change my order. I don’t want extra cheese on the last pizza I ordered (pizzaThree). So, the logic in the code is written to update the pizzaThree object instance as follows.

pizzaThree.ExtraCheese = false;

Now, let’s print my whole order to the console.

Console.WriteLine($"pizzaOne: {pizzaOne.Size} pizza with {string.Join(", ", pizzaOne.Toppings)}. Extra Cheese? {pizzaOne.ExtraCheese}");
Console.WriteLine($"pizzaTwo: {pizzaTwo.Size} pizza with {string.Join(", ", pizzaTwo.Toppings)}. Extra Cheese? {pizzaTwo.ExtraCheese}");
Console.WriteLine($"pizzaThree: {pizzaThree.Size} pizza with {string.Join(", ", pizzaThree.Toppings)}. Extra Cheese? {pizzaThree.ExtraCheese}");
pizzaOne: XL pizza with pepperoni, sausage, mushroom. Extra Cheese? False
pizzaTwo: XL pizza with ham, sausage, bacon. Extra Cheese? True
pizzaThree: XL pizza with pepperoni, sausage, mushroom. Extra Cheese? False

So now you can visualize the bug, when we changed pizzaThree to not have extra cheese, the program also changed pizzaOne to not have extra cheese. That’s because when initially declared the pizzaThree variable, we did not set it to a new instance of Pizza we set it to a reference of pizzaOne when means what every changes we make to the pizzaThree instance will be sent to the pizzaOne instance.

Creating a deep copy of an object instance

Let’s fix this bug by creating a deep copy of pizzaOne to create the pizzaThree instance.

Serialize the object we want to make the deep copy of:

using System.Text.Json;

var serializedPizzaOne = JsonSerializer.Serialize(pizzaOne);

Then, we can deserialize the object to the correct type and assign the object to the pizzaThree variable:

using System.Text.Json;

pizzaThree = JsonSerializer.Deserializer<Pizza>(serializedPizzaOne);

Great, now let’s not forget we don’t want extra cheese on pizzaThree:

pizzaThree.ExtraCheese = false;

Now let’s see if pizzaOne and pizzaThree are still equal:

Console.WriteLine(Object.Equals(pizzaOne, pizzaThree)); // False

Perfect, we expect these objects to be two different instances of the Pizza class.

Let’s check the order to ensure that pizzaThree is the same as pizzaOne but without extra cheese.

Console.WriteLine($"pizzaOne: {pizzaOne.Size} pizza with {string.Join(", ", pizzaOne.Toppings)}. Extra Cheese? {pizzaOne.ExtraCheese}");
Console.WriteLine($"pizzaTwo: {pizzaTwo.Size} pizza with {string.Join(", ", pizzaTwo.Toppings)}. Extra Cheese? {pizzaTwo.ExtraCheese}");
Console.WriteLine($"pizzaThree: {pizzaThree.Size} pizza with {string.Join(", ", pizzaThree.Toppings)}. Extra Cheese? {pizzaThree.ExtraCheese}");

Console

pizzaOne: XL pizza with pepperoni, sausage, mushroom. Extra Cheese? True
pizzaTwo: XL pizza with ham, sausage, bacon. Extra Cheese? True
pizzaThree: XL pizza with pepperoni, sausage, mushroom. Extra Cheese? False

It worked! By creating a deep copy of our reference type, we could update the ExtraCheese property of the pizzaThree instance without affecting the pizzaOne instance from which it was copied.

Conclusion

In this post, I shared a simple pizza order example to demonstrate reference types. Then, I showed you how to make a deep copy of a reference type using JSON serialization. Thanks for reading!