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.

To download the source code for the video, visit our Patreon page (YouTube Patron tier).

Let’s start.


VIDEO: Convert a C# Object to JSON Using System.Text.JSON and Newtonsont.JSON.

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


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.

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