In this article, we’re going to learn how to serialize a C# object into a JSON string using the two main JSON libraries in the .NET ecosystem. Also, we’re going to learn how to control different aspects of the serialization process.
Let’s start.
VIDEO: Convert a C# Object to JSON Using System.Text.JSON and Newtonsont.JSON.
Serialize C# Object Into JSON Strings Using System.Text.Json
Since .NET Core 3.0, System.Text.Json
is included in the framework by default. This is the official Microsoft JSON serialization and deserialization solution:
var obj = new Product { Name = "Red Apples", Stock = 100, DateAcquired = DateTime.Parse("2017-08-24") }; var jsonString = JsonSerializer.Serialize(obj);
Here, we see how we can use the Serialize()
static method of the JsonSerializer
to get the JSON string that results from an object passed as a parameter:
{"Name":"Red Apples","Stock":100,"DateAcquired":"2017-08-24T00:00:00"}
Using Newtonsoft Json.NET to Serialize C# Objects
In previous versions of the framework, the JSON library that came bundled with .NET was the Newtosoft Json.NET library:
var jsonString = JsonConvert.SerializeObject(obj);
Here, we turn an object into a JSON string by calling the SerializeObject()
static method of the JsonConvert
object.
How to Generate Pretty JSON Strings
Often, we’ll rather have indented or “pretty” JSON strings. We can easily achieve that with System.Text.Json
by using the WriteIntended
property of the JsonSerializerOptions
object:
var options = new JsonSerializerOptions { WriteIndented = true }; var jsonString = JsonSerializer.Serialize(obj, options);
In a similar fashion, Newtosoft has it even easier. We can include the Formatting.Indented
enumerated value directly in the call to the SerializedObject()
method:
var jsonString = JsonConvert.SerializeObject(obj, Formatting.Indented);
In both cases we will obtain a properly formatted JSON string:
{ "Name": "Red Apples", "Stock": 100, "DateAcquired": "2017-08-24T00:00:00" }
Camel Case Property Names
Despite being a common formatting option, none of these libraries will format property names using camel case by default. With System.Text.Json
, we need to explicitly set the PropertyNamingPolicy
option in our JsonSerializerOptions
object:
var options = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var jsonString = JsonSerializer.Serialize(obj, options);
Similarly, with Newtonsoft we need to populate the ContractResolver
property in our JsonSerializerSettings
with an instance of the CamelCasePropertyNamesContractResolver
class:
var options = new JsonSerializerSettings { Formatting = Formatting.Indented, ContractResolver = new CamelCasePropertyNamesContractResolver() }; var jsonString = JsonConvert.SerializeObject(obj, options);
When used this way, both libraries will generate camel case JSON properties when serializing objects:
{ "name": "Red Apples", "stock": 100, "dateAcquired": "2017-08-24T00:00:00" }
How to Ignore Certain Properties When Serializing Objects
Frequently, we’ll want to prevent some of our object’s properties from showing up in the resulting JSON string.
Ignoring NULL Values in Serialization
One specific scenario is when we do not want to include the properties that contain null
values. We can use JsonSerializerOptions
object in System.Text.Json
to set the DefaultIgnoreCondition
flag:
var obj = new Product { Name = "Red Apples", Stock = null, DateAcquired = null }; var options = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; var jsonString = JsonSerializer.Serialize(obj, options);
If we are using Newtonsoft, we’ll use the NullValueHandling
property in JsonSerializerSettings
:
var options = new JsonSerializerSettings { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }; var jsonString = JsonConvert.SerializeObject(obj, options); Console.WriteLine(jsonString);
The resulting JSON string will not include any of the null properties:
{ "Name": "Red Apples" }
Ignoring Specific Properties
These libraries include attributes that will let us provide serialization options on a per property basis. The most basic example of this is the JsonIgnore
attribute that exists in both System.Text.Json
and Newtonsoft.
We can use the attribute located in the System.Text.Json.Serialization
namespace on one or more properties in our object:
public class Customer { public string Name { get; set; } public string Address { get; set; } [JsonIgnore] public decimal FinancialRating { get; set; } }
Consequently, the resulting JSON string will not contain the FinancialRating
property even though it was present in the original object:
{ "Name": "Pumpkins And Roses", "Address": "158 Barfield Rd" }
If we are using Newtonsoft, we can achieve the same result by using the JsonIgnore
attribute located in the Newtonsoft.Json
namespace.
How to Serialize Anonymous and Dynamic Objects
Both libraries can handle anonymous objects correctly, out of the box:
var obj = new { FirstName = "John", LastName = "Smith" }; // System.Text.Json var jsonStringSystem = JsonSerializer.Serialize(obj, optionsSystem); // Newtonsoft var jsonStringNewtonsoft = JsonConvert.SerializeObject(obj, optionsNewtonsoft);
{ "FirstName": "John", "LastName": "Smith" }
Similarly, dynamic
objects work fine as well with both System.Text.Json
and Newtonsoft:
dynamic dynObj = new ExpandoObject(); dynObj.Name = "Corey Richards"; dynObj.Address = "3519 Woodburn Rd"; // System.Text.Json var jsonStringSystem = JsonSerializer.Serialize(dynObj, optionsSystem); // Newtonsoft var jsonStringNewtonsoft = JsonConvert.SerializeObject(dynObj, optionsNewtonsoft);
{ "Name": "Corey Richards", "Address": "3519 Woodburn Rd" }
How to Control Date and Time Format
System.Text.Json
will use ISO-8601-1:2019 format when serializing DateTime
or DateTimeOffset
properties in our objects, like in this example: 2017-08-24T16:59:57-02:00
. However, we can customize that by creating a custom converter:
public class GeneralDateTimeConverter : JsonConverter<DateTime> { public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return DateTime.ParseExact(reader.GetString() ?? string.Empty, "G", new CultureInfo("en-US")); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString("G", new CultureInfo("en-US"))); } }
By using this converter in our serialization code, we will get DateTime
properties in general date long time format 8/24/2017 16:59:57 AM
:
var options = new JsonSerializerOptions(); options.Converters.Add(new GeneralDateTimeConverter()); var obj = new { DateCreated = new DateTime(2017, 8, 24) }; var jsonString = JsonSerializer.Serialize(obj, options); // {"DateCreated":"8/24/2017 12:00:00 AM"}
Alternatively, we can apply our custom converter only to specific properties:
[JsonConverter(typeof(GeneralDateTimeConverter))] public DateTime BirthDate { get; set; }
In Newtonsoft, using a custom date and time format is done in a very similar way. We can create our custom converter that inherits from the Newtonsoft.Json.JsonConverter
abstract class:
public class GeneralDateTimeNewtonsoftConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(DateTime); } public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var dataString = reader.Value as string; return DateTime.ParseExact(dataString ?? string.Empty, "G", new CultureInfo("en-US")); } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { var dateTimeValue = value as DateTime?; writer.WriteValue(dateTimeValue?.ToString("G", new CultureInfo("en-US"))); } }
Once we have our custom converter we can use it either directly in the deserialization process:
var jsonString = JsonConvert.SerializeObject(obj, new GeneralDateTimeNewtonsoftConverter());
Or by decorating our object’s properties with the Newtonsoft.Json.JsonConverterAttribute
.
Reference Loops
To understand what a reference loop is, consider the following C# models:
public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public Department Department { get; set; } } public class Department { public string? Name { get; set; } public IList<Employee> Staff { get; set; } = new List<Employee>(); }
Here, we can see how the Employee
class references the Department
class that, in turn, references back to the Employee
class through its Staff
property.
When trying to turn a circular data structure into a JSON string, we will get an error no matter if we are using System.Text.Json
:
JsonException: A possible object cycle was detected.
Or Newtonsoft:
JsonSerializationException: Self referencing loop detected with type 'Employee'. Path 'Department.Staff'.
Options for Handling Reference Loops
The best way to avoid this situation is to design our models so they do not contain reference loops. However, if that’s not possible, JSON libraries offer options to help us deal with circular references.
For System.Text.Json
, we can set the JsonSerializerOptions.ReferenceHandler
property to one of the ReferenceHandler
enumerated value:
var employee = new Employee { FirstName = "John", LastName = "Smith" }; var department = new Department { Name = "Human Resources" }; employee.Department = department; department.Staff.Add(employee); var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve }; var jsonString = JsonSerializer.Serialize(department, options);
Here, we set the ReferenceHandler
property to ReferenceHandler.Preserve
. With this, we instruct the serializer to preserve the object graph structure by including metadata in the serialized objects, and resolve reverence loops using pointers based on generated unique object identifiers:
{ "$id": "1", "Name": "Human Resources", "Staff": { "$id": "2", "$values": [ { "$id": "3", "FirstName": "John", "LastName": "Smith", "Department": { "$ref": "1" } } ] } }
Alternatively, we can use the ReferenceHandler.IgnoreCycles
value to tell the serializer to simply ignore circular references:
{ "Name": "Human Resources", "Staff": [ { "FirstName": "John", "LastName": "Smith", "Department": null } ] }
On the other hand, with Newtonsoft our only option is to ignore the loop references using the JsonSerializerSettings.ReferenceLoopHandling
property:
var options = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
Conclusion
In this article, we have learned how to turn a C# object into a JSON string. For that, we learned how to use System.Text.Json and Newtonsoft Json.NET as well.
We’ve learned how to customize various aspects of the serialization process like indentation, property capitalization, how to ignore specific properties, or how to change the date and time format. We also learned what reference loops are and how to deal with them.
Finally, remember that we can find many other examples of how to work with System.Text.Json on this website.