This article will examine how we can get a formatted JSON representation of our C# objects using two popular libraries – Json.NET and System.Text.Json.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive right in!

Format JSON Using the Json.NET Library

Json.NET, part of the Newtonsoft.Json namespace, has been at the forefront of JSON serialization and deserialization for a long time. Numerous projects use it so it is almost mandatory for us to know our way around it.

Let’s set things up for our formatting journey:

public class Dog
{
    public string? Name { get; set; }
    public string? Breed { get; set; }
    public int Age { get; set; }
    public List<string>? FavoriteToys { get; set; }
    public List<string>? FavoriteFoods { get; set; }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this, Formatting.Indented);
    }
}

We start by declaring a Dog class in a new file with some simple properties – Name and Breed of string type, Age property of int type, and two List<string> type properties called FavoriteToys and FavoriteFoods. To get a serialized JSON string we can override the ToString method and return JsonConvert.SerializeObject(this), where this will be the current state of the Dog object once we initialize it.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

An important part here is using Newtonsoft.Json; at the beginning of our file – without it, we would not be able to use the JsonConvert class.

Now that we have our class we need a way to use it:

var rex = new Dog
{
    Name = "Rex",
    Breed = "German Shepherd",
    Age = 3,
    FavoriteToys = new List<string> { "Bone" },
    FavoriteFoods = new List<string> { "Beef", "Chicken" }
};

Console.WriteLine(rex);

In our Program class, we declare a new rex variable and assign a new instance of our Dog type to it. We also fill in the class’s properties with the data we wish. Finally, we print our Dog on the console which automatically calls the ToStringmethod we overrode:

{"Name":"Rex","Breed":"German Shephard","Age":3,"FavoriteToys":["Bone"],"FavoriteFoods":["Beef","Chicken"]}

We can see that all the relevant information is there, but it is all jumbled up into one line, making it hard to read if we have a more complex object.

Format JSON Using Formatting.Indented

There is one easy thing that we can do to format the serialized JSON:

public override string ToString()
{
    return JsonConvert.SerializeObject(this, Formatting.Indented);
}

In the ToString method of our Dog class, we pass one more parameter – Formatting.Indented, this will beautify our output by indenting it:

{
  "Name": "Rex",
  "Breed": "German Shepherd",
  "Age": 3,
  "FavoriteToys": [
    "Bone"
  ],
  "FavoriteFoods": [
    "Beef",
    "Chicken"
  ]
}

We can see that now it looks a lot better. 

Formatting is the thing that brings the most change to our output but there is another tool we can use – Attributes. If you are not familiar with them you can check our articles on Generic Attributes and Custom Attributes.

Format JSON Using Attributes

The attribute we can use for formatting purposes with Json.NET is JsonProperty. This attribute has several properties that we can take advantage of:

public class Dog
{
    [JsonProperty(PropertyName = "id")]
    public string? Name { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string? Breed { get; set; }
    public int Age { get; set; }
    [JsonProperty(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
    public List<string>? FavoriteToys { get; set; }
    [JsonProperty(Order = -2)]
    public List<string>? FavoriteFoods { get; set; }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this, Formatting.Indented);
    }
}

Here we add the attribute to three of our properties with different settings. First, for Breed we set NullValueHandling to ignore this property if it is null, then we set the NamingStrategyType of our FavoriteToys property to CamelCaseNamingStrategy and finally, we set the Order of FavoriteFoods to -2 which will make it appear first when serializing. 

We do one additional thing – set the Breed property in our Dog initialization to null, then we check what the serialization outputs:

{
  "FavoriteFoods": [
    "Beef",
    "Chicken"
  ],
  "id": "Rex",
  "Age": 3,
  "favoriteToys": [
    "Bone"
  ]
}

As expected FavoriteFoods comes first, Name is displayed as id, the Breed is not displayed, and favoriteToys uses CamelCaseNamingStrategy.

Format JSON Using the System.Text.Json Namespace

People at Microsoft have put a lot of work into expanding the support for JSON serialization and deserialization. This has resulted in massive improvements to the System.Text.Json namespace.

Let’s create another class and see what we can do with it when it comes to serialization:

public class Cat
{
    public string? Name { get; set; }
    public string? Breed { get; set; }
    public int Age { get; set; }
    public List<string>? FavoriteToys { get; set; }
    public List<string>? FavoriteFoods { get; set; }

    public override string ToString()
    {
        return JsonSerializer.Serialize(this);
    }
}

In a new file, we declare a new Cat class that is a replica of our initial Dog class except that here we return JsonSerializer.Serialize(this), which is System.Text.Json‘s namespace equivalent of JsonConvert.SerializeObject(this).

Now, we set things up in our Program class:

var felix = new Cat
{
    Name = "Felix",
    Breed = "Bengal",
    Age = 1,
    FavoriteToys = new List<string> { "Wool Ball", "Electric Mouse" },
    FavoriteFoods = new List<string> { "Chicken", "Fish" }
};

Console.WriteLine(felix);

We declare our felix variable as a new Cat and set the properties with our cat’s data.

Then we print it to the console:

{"Name":"Felix","Breed":"Bengal","Age":1,"FavoriteToys":["Wool Ball","Electric Mouse"],"FavoriteFoods":["Chicken","Fish"]}

Again we get the flat output. Next, we will see what we can do about that.

Format JSON Using JsonSerializerOptions

One powerful thing we get with this library is the serializer options:

public override string ToString()
{
    var options = new JsonSerializerOptions()
    {
        WriteIndented = true
    };

    return JsonSerializer.Serialize(this, options);
}

Here, we initialize a new options variable of JsonSerializerOptions type and set WriteIndented to true. After that, we pass the newly created options as a second parameter to the JsonSerializer, then we check the new output:

{
  "Name": "Felix",
  "Breed": "Bengal",
  "Age": 1,
  "FavoriteToys": [
    "Wool Ball",
    "Electric Mouse"
  ],
  "FavoriteFoods": [
    "Chicken",
    "Fish"
  ]
}

Nice, as with Json.NET, we get a huge visual improvement with WriteIndented.

This is not all we can do with JsonSerializerOptions so we take advantage of the various options provided by the System.Text.Json.Serialization namespace and further customize our JSON output:

public override string ToString()
{
    var options = new JsonSerializerOptions()
    {
        WriteIndented = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        NumberHandling = JsonNumberHandling.WriteAsString,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    };

    return JsonSerializer.Serialize(this, options);
}

We start by changing the naming policy to JsonNamingPolicy.CamelCase, which is the only out-of-the-box policy we get. Next, we can define how numbers are handled with JsonNumberHandling.WriteAsString – this will surround them with quotation marks. Finally, we also set the DefaultIgnoreCondition to not serialize values that are nullwith JsonIgnoreCondition.WhenWritingNull.

In the Program class, we set the FavoriteFoods to null and check what our updated options do:

{
  "name": "Felix",
  "breed": "Bengal",
  "age": "1",
  "favoriteToys": [
    "Wool Ball",
    "Electric Mouse"
  ]
}

The first thing that immediately sticks out is that the property names are now in the camel case style. We can also see that our cat’s age is displayed as "1" now and the FavoriteFoods are omitted – just as we expected them to be.

But this is far from all we can do, let’s further explore our options.

Format JSON Using Attributes

System.Text.Json also employs the use of pre-defined Attributes: 

public class Cat
{
    [JsonPropertyName("id")]
    public string? Name { get; set; }
    [JsonIgnore]
    public string? Breed { get; set; }
    [JsonPropertyOrder(-2)]
    public int Age { get; set; }
    public List<string>? FavoriteToys { get; set; }
    public List<string>? FavoriteFoods { get; set; }

    public override string ToString()
    {
        var options = new JsonSerializerOptions()
        {
            WriteIndented = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            NumberHandling = JsonNumberHandling.WriteAsString,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        };

        return JsonSerializer.Serialize(this, options);
    }
}

You might have guessed that we also have some useful Attributes at our disposal – JsonPropertyName to change the name of a property during serialization, JsonInclude and JsonIgnore to include or exclude a property as well as JsonPropertyOrder to set the order in which our properties are displayed. 

Let’s see how the output changes with our improvements:

{
  "age": "1",
  "id": "Felix",
  "favoriteToys": [
    "Wool Ball",
    "Electric Mouse"
  ]
}

Now, our Age property comes first, Name is renamed to id and Breed has been completely ignored even though its value is not null.

Global Serializer Settings

When our application grows in size, manually defining how the objects are serialized each time might become a nuisance. There is one feature, currently only present in Json.NET, that allows us to define our settings only once and then re-use them automatically every time:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
    Formatting = Formatting.Indented,
    NullValueHandling = NullValueHandling.Ignore,
    ContractResolver = new CamelCasePropertyNamesContractResolver()
};

In our Program file, we set the DefaultSettings of the JsonConvert class to the new custom default formatting settings we wish to use when serializing objects. To do that we need to pass a Func<JsonSerializerSettings> or in other words, a method that takes no parameters and returns an instance of the JsonSerializerSettings class.

We achieve that with a lambda expression that sets the Formatting to be indented, the NullValueHandling to ignore null value properties, and ContractResolver to a new instance of CamelCasePropertyNamesContractResolver which will display the properties in camel case style.

Let’s test this:

Console.WriteLine(JsonConvert.SerializeObject(rex));
Console.WriteLine(JsonConvert.SerializeObject(felix));

We use JsonConvert.SerializeObject to print the serialized output of our two pets, Rex and Felix, to the console. Note that we serialize the objects directly – we don’t use their respective ToString methods.

Then, we examine the output:

{
  "name": "Rex",
  "breed": "German Shepherd",
  "age": 3,
  "favoriteToys": [
    "Bone"
  ],
  "favoriteFoods": [
    "Beef",
    "Chicken"
  ]
}
{
  "name": "Felix",
  "breed": "Bengal",
  "age": 1,
  "favoriteToys": [
    "Wool Ball",
    "Electric Mouse"
  ],
  "favoriteFoods": [
    "Chicken",
    "Fish"
  ]
}

We can see that our two objects from different classes are serialized in the same way despite each having its separate formatting options in their ToString methods.

The DefaultSettings can be combined with the JsonProperty attribute we learned earlier to further customize the output – we still can re-order, change the name, and ignore properties if the need arises.

Conclusion

In this article, we learned that no matter the library used, we have a large degree of control over how our objects are serialized to JSON. Both libraries make it easy to output readable JSON by using indentation and both libraries also leverage Attributes to set various serialization and formatting options. Lastly, we learned how to set global serializer settings when we leverage the Json.NET library.

Regardless of the library you choose to work with you will be more than prepared to handle the needs of formatting your serialized JSON.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!