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
.
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.
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 ToString
method 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 null
with 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.