Code Review Videos > Learn C# > [Beginner’s Guide] JSON Serialization and Deserialization in C#

[Beginner’s Guide] JSON Serialization and Deserialization in C#

In this guide we are going to cover JSON serialization, which is the process of converting a C# object into a JSON string. And we are going to cover JSON deserialization, which is the reverse – taking a JSON string and converting it back to a C# object.

JSON (JavaScript Object Notation) is a lightweight data-interchange format that is used for exchanging data between web applications and servers. It is simple to read and write for humans, and easy for machines to parse and generate.

As a very quick example to get us started, a JSON string may look like this:

{
  "Title": "C# Developer",
  "Salary": 38000
}Code language: JSON / JSON with Comments (json)

C# is a modern, object-oriented programming language used for developing Windows applications and other Microsoft platforms.

And the equivalent C# object may look like this:

namespace JsonSerializeDeserialize.DeserializeExample.DTO.Easy;

public class Job
{
    public string? Title { get; set; }
    public int Salary { get; set; }
}Code language: C# (cs)

This page covers a variety of ways to convert between the two.

Serialization and deserialization are important and highly useful concepts in any programming language. This guide specifically looks at serialization and deserialization in C#, allowing us to convert objects into a format that can be stored or transmitted, and then converting them back into objects when needed.

JSON data is a particularly common format for web based systems communication.

The goal of this tutorial is to provide a beginner friendly, in-depth guide on how to serialize and deserialize JSON in C#. We will use the System.Text.Json library, which is the built-in JSON library in C#, to demonstrate how to perform these operations.

For each example we will include NUnit unit tests to verify the correctness of our code.

All code can be found on GitHub.

And for us Brits, yes I am aware there is no Z in (de)serialise. Blame our American friends across the pond.

Top Tip: Create C# Classes Automatically From Your JSON

If you already have a JSON payload that you want / need to work with in your C# code, then your IDE may be able to automatically convert that JSON into either a class or a record.

You can find out how to do that on this page.

Because who the heck wants to sit typing all that stuff, right?

Escaping JSON in C#

Before we dive into the examples, it is useful to understand the various ways that our raw JSON strings will be displayed.

For completeness, we will use all of these different approaches in the examples below.

There are several ways to write out a JSON string in C#. Each has its pros and cons.

Escaped With Backslashes

If you only have a small amount of JSON data to represent, you could define a JSON string as a string literal by enclosing the JSON data in double quotes. For example:

"{\"Title\":\"PHP Developer\",\"Salary\":55000}"Code language: JSON / JSON with Comments (json)

Notice that the start and end double quotes do not have a \ character before them, but all the others do.

The slash character before the double quote (\") is called an escape sequence. The problem this solves is that if we didn’t escape the double quote, C# would think we ended the string as soon as it finds a matching, unescaped double quote:

c# unescaped string literal error

Essentially here C# thinks we meant to write "{ "; and everything else is a mistake.

You can read up more on Escape Sequences on the official Microsoft C# documentation. Certain characters do need escaping to behave properly.

Escape With @ and ""

Perhaps more commonly you may come across JSON that looks like this:

const string example = @"{""Title"": ""C# Developer"", ""Salary"": 38000 }";Code language: C# (cs)

This is called a verbatim string literal.

Prior to C# version 11 this was the most convenient way of working with multi-line strings, strings that contain a backslash character that you actually meant (i.e. not an escape character), and in our case, embedded double quotes.

Whilst at a glance this does look very much like JSON, those double double quotes are still fiddly and annoying to add in your code editor.

Escape With """

New in C# 11 we can now use triple double quotes (aka raw string literals) to more easily create multi-line strings, and any string that would have needed an escape sequence.

c# 11 raw string literal

This looks and feels most like native JSON.

Raw string literals have a small number of quirks, but are generally very easy and straightforward to use. I would favour them if you the project is using C# 11.

Easy Example: Flat JSON

It’s tempting to try and jump in at the deep end. In my particular case, all of this exploration of JSON serialization and deserialization came about because the ChatGPT API returned a large, partly nested JSON payload and I wanted and needed to work with it in C#.

But I needed to understand the basics first. You can’t run before you can walk.

So first up we will look at serializing and deserializing flat JSON structures. An example being:

{ "Title": "C# Developer", "Salary": 38000 }Code language: C# (cs)

Once we can work with these, we will move on to harder (aka more real world) examples.

Deserializing JSON to C# Objects

Deserialization is the process of converting JSON data into C# objects.

If you have been doing a bit of Googling, or perhaps working on an older C# project, you may have come across the Newtonsoft.JSON library. I haven’t been around C# long enough to know the history here, but from the official Microsoft docs, the modern way to work with JSON in C# is using the built in System.Text.Json library.

The System.Text.Json library provides a simple and straightforward way to do this in C# that doesn’t need any external dependencies.

Here’s an example of how to deserialize JSON data into a C# object using System.Text.Json.

We need a basic object to create from our JSON data:

namespace JsonSerializeDeserialize.DeserializeExample.DTO.Easy;

public class Job
{
    public string? Title { get; set; }
    public int Salary { get; set; }
}Code language: C# (cs)

In this example, we define a Job class with Title and Salary properties. We then create a JSON string that represents a job, and use the JsonSerializer.Deserialize method to convert this JSON string into a Job object.

Here’s an NUnit test that verifies the deserialization process:

using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.Easy;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class EasyExampleTest
{
    [Test]
    public void Deserialize_ValidJsonString_ReturnsExpectedJobObject()
    {
        const string json = """
            { "Title": "C# Developer", "Salary": 38000 }
        """;

        var job = JsonSerializer.Deserialize<Job>(json);

        Assert.That(job.Title, Is.EqualTo("C# Developer"));
        Assert.That(job.Salary, Is.EqualTo(38_000));
    }
}Code language: C# (cs)

In this test, we created a JSON string that represents a job, and used the JsonSerializer.Deserialize method to convert this JSON string into a Job object.

Note that our Job class defined a string for Title, and the JSON data was also a string.

Also, the Job class defined an int for Salary, and the JSON data was a number (notice, no double quotes).

We then use NUnit assertions to verify that the properties of the Job object are set to the expected values.

Serializing C# Objects to JSON

Now let’s cover how to convert a C# object into JSON data. This is called serialization.

The process of serialization involves converting a C# object into a string that can be saved, transmitted, or manipulated as needed. You can serialize into any format you need. XML is another, thankfully less common format as an example.

To verify the serialization process, we can write a NUnit test as follows:

using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.Easy;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class EasyExampleTest
{
    [Test]
    public void Serialize_ValidJobObject_SerializesToExpectedJSONString()
    {
        var job = new Job { Salary = 55_000, Title = "PHP Developer" };

        const string expectedJson = "{\"Title\":\"PHP Developer\",\"Salary\":55000}";

        var actualJson = JsonSerializer.Serialize(job);

        Assert.That(actualJson, Is.EqualTo(expectedJson));
    }
}Code language: C# (cs)

In this example, we first create a Job object using the class we created earlier, with a title and salary.

We then use JsonSerializer.Serialize to convert the Job object into a JSON string.

Finally, we use an NUnit test to verify that the output of the serialization process is equal to the expected JSON string.

Note that the types of data are serialized as expected. In the JSON output we get a string value for the Title, and a numeric value for the Salary. This matches up with the types of data we have on our Job class properties for those two fields.

Harder Example: Nested JSON

The process we will follow here is no different to that seen in the “Easy Example” above.

The “harder” part comes because we have nested data. This is easier to understand with an example:

{
  "Recruiter": {
    "Name": "Ken Addams"
  },
  "Title": "C# Developer",
  "Salary": 38000,
  "Tags": [
    { "Name": "entity framework" },
    { "Name": "dot net" },
    { "Name": "azure" }
  ]
}Code language: JSON / JSON with Comments (json)

The Title and Salary key / values remain as before.

However, now we have a nested property in the form of Recruiter, and an array of nested data in the form of Tags.

Setup

We will need three things to hold our data. Three different types of data that will come together to represent our JSON as C# objects.

Previously we made use of a class. However, because we only really want to create a container / wrapper around our JSON as it converts to some form of C# object, a better data type to use would be a record. I wrote a little more about this on this page under the section titled “When Should I Use A Record Vs A Class”.

So for this example we will switch to using record types. But again, don’t over think this, whether we used a record or a class here, the outcome would not change.

using System.Collections.Immutable;

namespace JsonSerializeDeserialize.DeserializeExample.DTO.Harder;

public record Job
{
    public required string Title { get; init; }
    public required int Salary { get; init; }
    public required ImmutableList<Tag> Tags { get; init; }
    public required Recruiter Recruiter { get; init; }
}

public record Recruiter
{
    public required string Name { get; init; }
}

public record Tag
{
    public required string Name { get; init; }
}Code language: C# (cs)

The required modifier is used to indicate that the Name property must have a non-null value during construction.

The init keyword is used to make the property read-only after construction. This means that the property’s value can be set during construction but not modified afterwards, making the record immutable by default.

The combination of required and init is useful for defining properties that must have a value and shouldn’t be changed after construction.

You may be thinking that you absolutely do want to change your data. That’s fine. And that can come later. What is happening at this stage is the very isolated process of converting from JSON to C#. Any further transformations should happen elsewhere in your project.

Deserializing JSON to C# Objects

All of the hard work happened in the Setup step above.

using System.Collections.Immutable;
using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.Harder;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class HarderExampleTest
{
    [Test]
    public void Deserialize_ValidJsonString_ReturnsExpectedJobRecruiterAndTagsObjects()
    {
        const string json = """
        {
          "Recruiter": {
            "Name": "Ken Addams"
          },
          "Title": "C# Developer",
          "Salary": 38000,
          "Tags": [
            { "Name": "entity framework" },
            { "Name": "dot net" },
            { "Name": "azure" }
          ]
        }
        """;

        var job = JsonSerializer.Deserialize<Job>(json);

        Assert.That(job.Title, Is.EqualTo("C# Developer"));
        Assert.That(job.Salary, Is.EqualTo(38_000));
        Assert.That(job.Recruiter, Is.InstanceOf<Recruiter>());
        Assert.That(job.Recruiter.Name, Is.EqualTo("Ken Addams"));
        Assert.That(job.Tags, Has.Count.EqualTo(3));
        Assert.That(job.Tags[0].Name, Is.EqualTo("entity framework"));
        Assert.That(job.Tags[1].Name, Is.EqualTo("dot net"));
        Assert.That(job.Tags[2].Name, Is.EqualTo("azure"));
    }
}Code language: C# (cs)

We had to define our object structure, and then the process of deserializing the raw JSON string into their respective objects was handled entirely by JsonSerializer.Deserialize<Job>(json).

Serializing C# Objects to JSON

As above, serializing from a C# object hierarchy to a JSON string is no different to our “Easy Example”.

using System.Collections.Immutable;
using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.Harder;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class HarderExampleTest
{
    [Test]
    public void Serialize_ValidJobRecruiterAndTagsObject_SerializesToExpectedJSONString()
    {
        var job = new Job
        {
            Title = "Kotlin Developer",
            Salary = 85_950,
            Recruiter = new Recruiter { Name = "Alan Johnson" },
            Tags = ImmutableList.Create(
                new Tag { Name = "java" },
                new Tag { Name = "android" }
            )
        };

        const string expectedJson =
            @"{""Title"":""Kotlin Developer"",""Salary"":85950,""Tags"":[{""Name"":""java""},{""Name"":""android""}],""Recruiter"":{""Name"":""Alan Johnson""}}";

        var actualJson = JsonSerializer.Serialize(job);

        Assert.That(actualJson, Is.EqualTo(expectedJson));
    }
}
Code language: C# (cs)

As long as we can create the object structure in C#, creating the JSON representation is no different regardless of whether we have to serialize a single object or one that has a lot of nested properties.

Missing & Unexpected Properties

What happens if you receive a JSON string that is missing a property, or it has additional property?

Well, how you handle that is probably down to your specific use case.

It may be best that you do not accept JSON when it is missing some or all the values you are expecting.

Or you may wish to accept whatever you can get your hands on.

We’ve actually already covered how to handle the first case, but we will explicit cover it off again here.

Setup

Here is our basic data container:

namespace JsonSerializeDeserialize.DeserializeExample.DTO.MissingProperties;

public record Animal
{
    public string? Name { get; init; }
    public int Age { get; init; }
    public double Weight { get; init; } = 36.3;
}Code language: C# (cs)

This code defines a record type named Animal.

The Animal type has three properties:

  • Name, of type string? (nullable string). This means that the Name property can have a value of null.
  • Age, of type int.
  • Weight, of type double. This property has a default value of 36.3.

All properties are marked with the init keyword, making them read-only after construction. This makes the record immutable by default.

In order to make this example work, we cannot use the required keyword as we have previously. If a property is marked as required then we must provide a value at the point of construction. The whole point here is that we don’t have a value to provide.

However, just because we don’t provide a value, doesn’t mean that a value will not be set. C# defines a default value depending on the type of the property.

The default values here would be:

  • null if Name is not provided – this is the C# default value for string
  • 0 if Age is not provided – this is the C# default value for int
  • 36.3 if Weight is not provided, because we explicitly provided a default

Armed with this knowledge, we will now see what happens during serialization and deserialization when values are missing, or additional values are provided.

Deserializing JSON to C# Objects

As with all these examples, the “hard part” is done during the setup step above.

using JsonSerializeDeserialize.DeserializeExample.DTO.MissingProperties;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class MissingPropertiesTest
{
    [Test]
    public void Deserialize_PartialJsonString_ReturnsAnimal()
    {
        // the "Unexpected" key will be silently discarded
        const string json = """
            { "Name": "Pebbles", "Unexpected": "value" }
        """;

        var animal = JsonSerializer.Deserialize<Animal>(json);

        Assert.That(animal.Name, Is.EqualTo("Pebbles"));
        Assert.That(animal.Age, Is.EqualTo(0));
        Assert.That(animal.Weight, Is.EqualTo(36.3));
    }

    [Test]
    public void Deserialize_EmptyJsonString_ReturnsAnimal()
    {
        const string json = "{}";

        var animal = JsonSerializer.Deserialize<Animal>(json);

        Assert.That(animal.Name, Is.EqualTo(null));
        Assert.That(animal.Age, Is.EqualTo(0));
        Assert.That(animal.Weight, Is.EqualTo(36.3));
    }
}Code language: C# (cs)

In the above examples we can see that any additional values in the given JSON are silently ignored.

If valid key / values are provided in the JSON, they are processed and applied as in all other examples.

If a value is missing – and allowed to be missing (aka not required) then that property will be set to its default value.

Serializing C# Objects to JSON

This same logic applies both ways.

If we serialize an object that has default values then they will simply output as that value. From the Serialize point of view, it doesn’t matter if the value is provided by us, or the default value provided by C#.

using JsonSerializeDeserialize.DeserializeExample.DTO.MissingProperties;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class MissingPropertiesTest
{
    [Test]
    public void Serialize_EmptyAnimalObject_SerializesToExpectedJSONString()
    {
        var animal = new Animal();

        var actualJson = JsonSerializer.Serialize(animal);

        Assert.That(actualJson, Is.EqualTo(""""{"Name":null,"Age":0,"Weight":36.3}""""));
    }

    [Test]
    public void Serialize_PartialAnimalObject_SerializesToExpectedJSONString()
    {
        var animal = new Animal() { Weight = 99.9 , Age = 14 };

        var actualJson = JsonSerializer.Serialize(animal);

        Assert.That(actualJson, Is.EqualTo(""""{"Name":null,"Age":14,"Weight":99.9}""""));
    }
}Code language: C# (cs)

Pretty JSON Output & camelCaseKeys

Unless you live in a Microsoft bubble, where your IDE SHOUTS AT YOU with its uppercase menu, and you enjoy Everything Having An Uppercase Character To Start With, then you will probably be thinking… blurghh, that JSON looks ugly.

Out there in the real world, it’s pretty rare to come across a JSON API that expects keys to start with an uppercase letter. In fact, I can only ever think of one time in my regular day job where I encountered this, and I figured it was some kind of mistake. It was a good while later that I came to understand that it was probably a C# API I was hitting back there.

Anyway, yeah, back in the real world you probably don’t want to force your API consumers to have to use uppercase letters for keys. It turns out this is possible in C#, but it’s a little … long winded to make it work that way.

And heck, it might be nice to output JSON in a pretty printed, human friendly format. Rather than as one long line that only computers enjoy.

Deserializing JSON to C# Objects

All we want to do here is take JSON as input where the key is in lower case, and map that onto our C# object where we use Title case.

More concretely: title in our JSON should become Title on our C# object:

using System.Collections.Immutable;
using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.Harder;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class PrettyExampleTest
{
    [Test]
    public void Deserialize_ValidPrettyJsonString_ReturnsExpectedJobAndRecruiterObject()
    {
        const string json = """
        {
          "recruiter": {
            "name": "Malcolm Tucker"
          },
          "title": "Java Developer",
          "salary": 44450,
          "tags": [
            { "name": "spring framework" },
            { "name": "abstract singleton factory bean" }
          ]
        }
        """;

        var job = JsonSerializer.Deserialize<Job>(json, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        });

        Assert.That(job.Title, Is.EqualTo("Java Developer"));
        Assert.That(job.Salary, Is.EqualTo(44_450));
        Assert.That(job.Recruiter, Is.InstanceOf<Recruiter>());
        Assert.That(job.Recruiter.Name, Is.EqualTo("Malcolm Tucker"));
        Assert.That(job.Tags, Has.Count.EqualTo(2));
        Assert.That(job.Tags[0].Name, Is.EqualTo("spring framework"));
        Assert.That(job.Tags[1].Name, Is.EqualTo("abstract singleton factory bean"));
    }
}
Code language: C# (cs)

Lines 12-24 show that our incoming JSON structure has the right key names, but they all begin with a lowercase letter.

Lines 26-29 show how to set up your instance of JsonSerializer such that it can parse those keys and translate them on to the correct object, just like before.

Serializing C# Objects to JSON

We’re going to do the same thing now that we have been doing all along.

The difference is that we’re going to output our JSON with keys that begin with a lower case letter, and we are going to pretty print the output:

using System.Collections.Immutable;
using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.Harder;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class PrettyExampleTest
{
    [Test]
    public void Serialize_ValidJobAndRecruiterObject_SerializesToExpectedPrettyJSONString()
    {
        var job = new Job
        {
            Title = "Kotlin Developer",
            Salary = 85_950,
            Recruiter = new Recruiter { Name = "Alan Johnson" },
            Tags = ImmutableList.Create(
                new Tag { Name = "java" },
                new Tag { Name = "android" }
            )
        };

        const string expectedJson = """
        {
          "title": "Kotlin Developer",
          "salary": 85950,
          "tags": [
            {
              "name": "java"
            },
            {
              "name": "android"
            }
          ],
          "recruiter": {
            "name": "Alan Johnson"
          }
        }
        """;

        var actualJson = JsonSerializer.Serialize(job, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = true,
        });

        Assert.That(actualJson, Is.EqualTo(expectedJson));
    }
}
Code language: C# (cs)

Really then, no difference to our process, we just customised the JsonSerializer.Serialize options.

Custom Property Names

What if the JSON naming structure doesn’t match with your object naming structure on a 1:1 basis?

Well, I guess there are arguments to be had here. But I’m not going to have them, I’m just going to focus on how to achieve this, if this is what you want to achieve.

The problem here is that we have JSON like this:

 { "fore_name": "Timmy", "surname": "Testfield Snr" }Code language: JSON / JSON with Comments (json)

But we have a C# data type like this:

namespace JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyNames;

public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
};Code language: JavaScript (javascript)

Oh dear.

  • fore_name needs to map to FirstName
  • surname needs to map to LastName

How can we fix this?

Setup

Once more this problem is solved at the C# level, in this case by adding the JsonPropertyName attribute to the respective property on our record:

using System.Text.Json.Serialization;

namespace JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyNames;

public record Person
{
    [JsonPropertyName("fore_name")]
    public required string FirstName { get; init; }

    [JsonPropertyName("surname")]
    public required string LastName { get; init; }
};
Code language: C# (cs)

The JsonPropertyName attribute maps the FirstName property in C# to a JSON property named fore_name when serializing or deserializing the record.

This allows for mapping between property names in C# and the equivalent property names in JSON.

Note the addition of the using System.Text.Json.Serialization on Line 1, which is the where the JsonPropertyName attribute comes from.

Also note that you cannot mix and match here. If you decorate a property with the JsonPropertyName attribute, you have to use that name. For example you can’t refer to surname as LastName in your JSON in the above example, it has to always be surname.

Deserializing JSON to C# Objects

Just like in all other examples, the actual deserialization process is very easy once we have decorated our record‘s properties with the JsonPropertyName attribute:

using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyNames;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class CustomPropertyNamesTest
{
    [Test]
    public void Deserialize_VariantJsonStringKeys_ReturnsExpectedPersonObject()
    {
        const string json = """
            { "fore_name": "Timmy", "surname": "Testfield Snr" }
        """;

        var person = JsonSerializer.Deserialize<Person>(json);

        Assert.That(person.FirstName, Is.EqualTo("Timmy"));
        Assert.That(person.LastName, Is.EqualTo("Testfield Snr"));
    }
}Code language: C# (cs)

Serializing C# Objects to JSON

And again, serialization is identical to all other examples:

using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyNames;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class CustomPropertyNamesTest
{
    [Test]
    public void Serialize_ValidJobObject_SerializesToExpectedJSONString()
    {
        var person = new Person { FirstName = "Barry", LastName = "Ireland" };

        const string expectedJson = "{\"fore_name\":\"Barry\",\"surname\":\"Ireland\"}";

        var actualJson = JsonSerializer.Serialize(person);

        Assert.That(actualJson, Is.EqualTo(expectedJson));
    }
}Code language: C# (cs)

Easy, right?

Custom Property Order

When your C# objects are converted to JSON, does it trigger you that the keys are in some unexpected order?

Let’s say you have this record:

namespace JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyOrder;

public record Alphabet
{
    public required string B { get; init; }
    public required string A { get; init; }
    public required string C { get; init; }
};Code language: JavaScript (javascript)

Now, it’s already annoying me that this isn’t in the order A, B, C, but trust me, this sort of thing happens all over the real world code I work with.

And of course what happens here when you serialize this object to JSON is you get:

{"B":"B is here also","A":"hello from A","C":"USB C"}Code language: JSON / JSON with Comments (json)

Why God? Why?

Well, it’s because C# serializes in order it finds the properties. No big deal, and actually very logical. The JSON spec does not define an order anyway, so this is technically spot on.

Anyway, it may bug the hell out of you.

As you can probably tell, it bugs me.

Obviously, the easy fix here is to re-order the properties:

namespace JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyOrder;

public record Alphabet
{
    public required string A { get; init; }
    public required string B { get; init; }
    public required string C { get; init; }
};Code language: C# (cs)

Which gives:

{"A":"hello from A","B":"B is here also","C":"USB C"}Code language: C# (cs)

OK, so far so good.

But what about this object:

namespace JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyOrder;

public record Alphabet
{
    public required string A { get; init; }
    public required string B { get; init; }
    public required string C { get; init; }
    public required string ID { get; init; }
};Code language: C# (cs)

There I am telling you I like my JSON to be alphabetically, even when it needn’t be, and then I come at you with a very common scenario that goes against everything I just said.

If there’s an ID, I like that being the very first value I see. It just makes sense. Oh, and life a bit easier.

Sure, I could move that to the top of my record, but that is no longer alphabetical. Oh my, what a conundrum.

Setup

To ‘fix’ this, we will use two examples.

using System.Text.Json.Serialization;

namespace JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyOrder;

public record Alphabet
{
    public required string A { get; init; }
    public required string B { get; init; }
    public required string C { get; init; }
};

public record Alphabodge
{
    [JsonPropertyOrder(3)]
    public required string A { get; init; }

    [JsonPropertyOrder(1)]
    public required string B { get; init; }

    [JsonPropertyOrder(2)]
    public required string C { get; init; }
};Code language: JavaScript (javascript)

You have likely already figured out what will happen, but for completeness, let’s cover both serialization and deserialization one last time.

Deserializing JSON to C# Objects

Again, exactly the same process as all other examples. All the hard work is done up front in the Setup step.

Note here that it doesn’t matter what order the keys come in as JSON:

using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyOrder;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class CustomPropertyOrderTest
{
    [Test]
    public void Deserialize_UnorderedJsonStringKeys_ReturnsExpectedAlphabetObject()
    {
        const string json = """
            { "C": "this is C", "B": "B is here", "A": "hello from A" }
        """;

        var alphabet = JsonSerializer.Deserialize<Alphabet>(json);

        Assert.That(alphabet.A, Is.EqualTo("hello from A"));
        Assert.That(alphabet.B, Is.EqualTo("B is here"));
        Assert.That(alphabet.C, Is.EqualTo("this is C"));
    }

    [Test]
    public void Deserialize_UnorderedJsonStringKeys_ReturnsExpectedAlphabodgeObject()
    {
        const string json = """
            { "C": "this is C", "B": "B is here", "A": "hello from A" }
        """;

        var alphabodge = JsonSerializer.Deserialize<Alphabodge>(json);

        Assert.That(alphabodge.A, Is.EqualTo("hello from A"));
        Assert.That(alphabodge.B, Is.EqualTo("B is here"));
        Assert.That(alphabodge.C, Is.EqualTo("this is C"));
    }
}
Code language: C# (cs)

Deserializing doesn’t care about key order at all.

That means the more interesting case here is…

Serializing C# Objects to JSON

The differences here are in the order of the output.

Previously we left this up to the property order as defined in our underlying C# data type. But now, with the explicit use of the JsonPropertyOrder attribute we can set the order however we want.

using System.Text.Json;
using JsonSerializeDeserialize.DeserializeExample.DTO.CustomPropertyOrder;

namespace JsonSerializeDeserializeTest.DeserializeExample;

public class CustomPropertyOrderTest
{
    [Test]
    public void Serialize_ValidAlphabetObject_SerializesToExpectedDefaultOrderedJSONString()
    {
        var alphabet = new Alphabet { A = "hello from A", B = "B is here also", C = "USB C"};

        const string expectedJson = "{\"A\":\"hello from A\",\"B\":\"B is here also\",\"C\":\"USB C\"}";

        var actualJson = JsonSerializer.Serialize(alphabet);

        Assert.That(actualJson, Is.EqualTo(expectedJson));
    }

    [Test]
    public void Serialize_ValidAlphabodgeObject_SerializesToExpectedDefaultOrderedJSONString()
    {
        var alphabodge = new Alphabodge { A = "hello from A", B = "B is here also", C = "USB C"};

        const string expectedJson = "{\"B\":\"B is here also\",\"C\":\"USB C\",\"A\":\"hello from A\"}";

        var actualJson = JsonSerializer.Serialize(alphabodge);

        Assert.That(actualJson, Is.EqualTo(expectedJson));
    }
}Code language: C# (cs)

Conclusion

In this tutorial, we covered how to do JSON serialization and deserialization in C#. We explored the System.Text.Json library, and demonstrated various common examples of serializing and deserializing JSON to/from C# objects.

We looked at some common tasks such as handling missing and additional data on a given JSON string, and covered how to format the JSON that we are outputting.

Overall we saw that the process for serialization and deserialization of JSON in C# is always the same. The real work happens when setting up the objects that we serialize / deserialize. That is why it is best to make these objects very specific – using Data Transfer Objects (DTO) – for individual use cases.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.