C# – Changing the JSON serialization date format

When you serialize a date with System.Text.Json, it uses the standard ISO-8601 date format (ex: “2022-01-31T13:15:05.2151663-05:00”). Internally, it uses the built-in DateTimeConverter class for handling DateTime, which doesn’t give you a way to change the date format.

To change the date format, you have to create a custom JSON converter and pass it in:

using System.Text.Json;

var options = new JsonSerializerOptions() { WriteIndented = true };
options.Converters.Add(new CustomDateTimeConverter("yyyy-MM-dd"));

var nikolaTesla = new Person() { BirthDate = new DateTime(year: 1856, month: 7, day: 10) };

var json = JsonSerializer.Serialize(nikolaTesla, options);
Console.WriteLine(json);
Code language: C# (cs)

This converter class handles converting a DateTime to a string during serialization and the reverse during deserialization (parses the DateTime from a string). This allows you to use any date format you want.

using System.Text.Json;
using System.Text.Json.Serialization;

public class CustomDateTimeConverter : JsonConverter<DateTime>
{
	private readonly string Format;
	public CustomDateTimeConverter(string format)
	{
		Format = format;
	}
	public override void Write(Utf8JsonWriter writer, DateTime date, JsonSerializerOptions options)
	{
		writer.WriteStringValue(date.ToString(Format));
	}
	public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		return DateTime.ParseExact(reader.GetString(), Format, null);
	}
}
Code language: C# (cs)

Note: If you need to deal with DateTimeOffset as well, you’ll need another custom converter. Consider using JsonConverterFactory in that scenario.

Running the code above generates the following JSON with the custom date format:

{
  "BirthDate": "1856-07-10"
}Code language: JSON / JSON with Comments (json)

Newtonsoft – Change date format through settings

It’s much simpler to change the date format when you’re using Newtonsoft. By default, it uses the ISO-8601 date format, but you can change it by setting the DateFormatString setting:

using Newtonsoft.Json;

var settings = new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd" };

var nikolaTesla  = new Person() { BirthDate = new DateTime(year: 1856, month: 7, day: 10) };

var json = JsonConvert.SerializeObject(nikolaTesla, Formatting.Indented, settings);
Console.WriteLine(json);
Code language: C# (cs)

This outputs the following JSON:

{
  "BirthDate": "1856-07-10"
}Code language: JSON / JSON with Comments (json)

Handling DateOnly and TimeOnly

Starting in .NET 7, System.Text.Json supports (de)serialization of DateOnly/TimeOnly properties. Here’s an example:

using System.Text.Json;

var activity = new Activity()
{
    Date = new DateOnly(year: 2022, month: 1, day: 31),
    Time = new TimeOnly(hour: 14, minute: 39)
};

var json = JsonSerializer.Serialize(activity, new JsonSerializerOptions() { WriteIndented = true });
Console.WriteLine($"Serialized DateOnly/TimeOnly={json}");

var newActivity = JsonSerializer.Deserialize<Activity>(json);
Console.WriteLine($"Deserialized DateOnly={newActivity.Date} TimeOnly={newActivity.Time}");
Code language: C# (cs)

This outputs the following, showing that it properly handles (de)serializing DateOnly/TimeOnly properties:

Serialized DateOnly/TimeOnly={
  "Date": "2022-01-31",
  "Time": "14:39:00"
}
Deserialized DateOnly=1/31/2022 TimeOnly=2:39 PMCode language: plaintext (plaintext)

Before .NET 7 – Handling DateOnly/TimeOnly with System.Text.Json custom converters

The DateOnly/TimeOnly types were not initially supported by System.Text.Json when they were introduced in .NET 6. When you tried to use these, you’d get an exception:

System.NotSupportedException: Serialization and deserialization of ‘System.DateOnly’ instances are not supported.

To be able to handle DateOnly and TimeOnly, you’d have to create and use custom converters, like this:

using System.Text.Json;

var options = new JsonSerializerOptions() { WriteIndented = true };
options.Converters.Add(new CustomDateOnlyConverter("yyyy-MM-dd"));
options.Converters.Add(new CustomTimeOnlyConverter("HH:mm"));

var activity = new Activity()
{
    Date = new DateOnly(year: 2022, month: 1, day: 31),
    Time = new TimeOnly(hour: 14, minute: 39)
};

var json = JsonSerializer.Serialize(activity, options);
Console.WriteLine(json);
Code language: C# (cs)

This outputs the following JSON:

{
  "Date": "2022-01-31",
  "Time": "14:39"
}Code language: JSON / JSON with Comments (json)

Here are the DateOnly and TimeOnly custom converter classes:

using System.Text.Json;
using System.Text.Json.Serialization;

public class CustomDateOnlyConverter : JsonConverter<DateOnly>
{
	private readonly string Format;
	public CustomDateOnlyConverter(string format)
	{
		Format = format;
	}
	public override void Write(Utf8JsonWriter writer, DateOnly date, JsonSerializerOptions options)
	{
		writer.WriteStringValue(date.ToString(Format));
	}
	public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		return DateOnly.ParseExact(reader.GetString(), Format);
	}
}

public class CustomTimeOnlyConverter : JsonConverter<TimeOnly>
{
	private readonly string Format;
	public CustomTimeOnlyConverter(string format)
	{
		Format = format;
	}
	public override void Write(Utf8JsonWriter writer, TimeOnly date, JsonSerializerOptions options)
	{
		writer.WriteStringValue(date.ToString(Format));
	}
	public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		return TimeOnly.ParseExact(reader.GetString(), Format);
	}
}
Code language: C# (cs)

Newtonsoft and DateOnly/TimeOnly properties

Starting with v13.0.2, Newtonsoft handles (de)serialization of DateOnly/TimeOnly properly. Here’s an example:

using Newtonsoft.Json;

var activity = new Activity()
{
    Date = new DateOnly(year: 2022, month: 1, day: 31),
    Time = new TimeOnly(hour: 14, minute: 39)
};

var json = JsonConvert.SerializeObject(activity, Formatting.Indented);
Console.WriteLine("Newtonsoft serializes DateOnly/TimeOnly properly now");
Console.WriteLine(json);

var newActivity = JsonConvert.DeserializeObject<Activity>(json);
Console.WriteLine("Newtonsoft can deserialize DateOnly/TimeOnly");
Console.WriteLine($"DateOnly={newActivity.Date} TimeOnly={newActivity.Time}");
Code language: C# (cs)

This outputs the following, showing that Newtonsoft handles TimeOnly/DateOnly as expected:

Newtonsoft serializes DateOnly/TimeOnly properly now
{
  "Date": "2022-01-31",
  "Time": "14:39"
}
Newtonsoft can deserialize DateOnly/TimeOnly
DateOnly=1/31/2022 TimeOnly=2:39 PMCode language: plaintext (plaintext)

Before v13.0.2 – Newtonsoft doesn’t handle DateOnly / TimeOnly well

When DateOnly/TimeOnly were first introduced in .NET 6, Newtonsoft didn’t handle them properly. Unlike System.Text.Json, Newtonsoft attempted to handle DateOnly/TimeOnly without throwing an exception, but the results were undesirable. Here’s an example to see what I mean:

using Newtonsoft.Json;

var nikolaTesla = new Person() { BirthDate = new DateOnly(year: 1856, month: 7, day: 10) };

var json = JsonConvert.SerializeObject(nikolaTesla, Formatting.Indented);
Console.WriteLine(json);
Code language: C# (cs)

This outputs the following JSON:

{
  "BirthDate": {
    "Year": 1856,
    "Month": 7,
    "Day": 10,
    "DayOfWeek": 4,
    "DayOfYear": 192,
    "DayNumber": 677715
  }
}Code language: JSON / JSON with Comments (json)

It’s really just serializing all of DateOnly’s public properties. I don’t think anyone would want to receive a date like this. Furthermore, it wasn’t able to deserialize this back into a DateOnly property because the properties have non-public setters. Before the latest version that supports DateOnly/TimeOnly, you’d need to write a custom converter (same approach as System.Text.Json) to handle DateOnly / TimeOnly properly.

Leave a Comment