Force JSON.Net to Include Milliseconds When Serializing Datetime (Even If Ms Component Is Zero)

Force JSON.NET to include milliseconds when serializing DateTime (even if ms component is zero)

We ran into this same issue on my current project. We are using Web API (and hence JSON.Net) to implement a REST API. We discovered that, when serializing DateTime objects, JSON.Net omits the trailing zeros from the milliseconds, or omits the milliseconds from the date entirely if it is zero. Our clients were expecting a fixed-length date-time string, with exactly 3 digits for the milliseconds. We fixed it by doing the following in Application_Start():

JsonSerializerSettings settings = HttpConfiguration.Formatters.JsonFormatter.SerializerSettings;
IsoDateTimeConverter dateConverter = new IsoDateTimeConverter
{
DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'"
};
settings.Converters.Add(dateConverter);

If you're not using Web API, you can do the same thing by creating a new instance of JsonSerializerSettings, adding the IsoDateTimeConverter to it as shown above, then passing the serializer settings to JsonConvert.SerializeObject().

Note: If you're serializing a DateTimeOffset or a local DateTime and you want to include the timezone offset, replace the quoted 'Z' in the above format with an unquoted K.
See Custom Date and Time Format Strings in the documentation for more info.

Force JSON.NET to include milliseconds AND output null for a null date field when serializing DateTime

The issue is that the ReadJson and WriteJson methods in your MinDateTimeConverter do not call the base class, so the normal processing that handles the custom date format in the non-null case never happens. Here is the corrected code:

public class MinDateTimeConverter : IsoDateTimeConverter
{
public MinDateTimeConverter()
{
DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'";
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(DateTime) && reader.TokenType == JsonToken.Null)
return DateTime.MinValue;

return base.ReadJson(reader, objectType, existingValue, serializer);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is DateTime date && date == DateTime.MinValue)
{
writer.WriteNull();
return;
}

base.WriteJson(writer, value, serializer);
}
}

Fiddle: https://dotnetfiddle.net/x8PAzf

DateTime serializers omitting milliseconds?

JSON.NET and System.Text.Json is breaking the specification, because we are sending different 'formats'?

JSON isn’t an “adopting standard” as it does not reference ISO 8601, or prescribe any particular format for date and time. In JSON they are just strings. So serializers are free to represent dates in any way they like. Both serializers choose ISO 8601, and fractional seconds are not required and often useless extra bytes.

This is the downside of JSON being radically simple.

System.Text.Json has custom converters you can use to override the default behavior of the serializer: How to write custom converters for JSON serialization (marshalling) in .NET

Json.Net messes up timezones for DateTimeOffset when serializing

In your code, you are doing the following:

  1. Serializing an instance of Class2 to a JSON string using specific DateTime-related serialization settings.
  2. Deserializing to a JToken hierarchy without using those settings.
  3. (Making additional modifications to the hierarchy - not shown.)
  4. Serializing the JToken hierarchy to a final string (via json.ToString()) again without using those settings.

When you do, formatting settings for dates chosen in step #1 get lost.

To solve this, you need to apply the settings every time you serialize from or to a JSON string representation, since, as explained in this documentation page, JSON does not have an "official" format for dates. Because of this, Json.NET applies heuristics for recognizing and formatting dates whenever it converts from and to a JSON string representation - which you are doing not once but thrice.

You could accomplish this by doing:

var settings = new JsonSerializerSettings()
{
DateParseHandling = DateParseHandling.DateTimeOffset,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc
};

// Generate initial serialization
var initialString = JsonConvert.SerializeObject(foo, settings);

// Parse back to JToken
var json = JsonConvert.DeserializeObject<JObject>(initialString, settings);

// Make modifications as required
// json["foo"] = "bar";

// Generate final JSON.
var finalString = JsonConvert.SerializeObject(json, Formatting.Indented, settings);

To improve efficiency, you could use JToken.FromObject() (or JObject.FromObject() if you prefer) to generate the JToken hierarchy without needing to create and parse an initial string representation:

var settings = new JsonSerializerSettings()
{
DateParseHandling = DateParseHandling.DateTimeOffset,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc
};

var json = JToken.FromObject(foo, JsonSerializer.CreateDefault(settings));

// Make modifications as required
// json["foo"] = "bar";

// Generate final JSON.
var finalString = JsonConvert.SerializeObject(json, Formatting.Indented, settings);

Note, however, that Json.NET will output a UTC DateTime in the format "0047-06-20T15:00:00Z" rather than "2016-06-19T15:00:00+00:00" for reasons explained here. If you need your UTC DateTime properties to be serialized in DateTimeOffset format you might need to use a custom converter.

JSON Date and DateTime serialisation in c# & newtonsoft

As I mentioned in a comment, there is no standard date representation in JSON. The ISO8601 is the de-facto standard, ie most people started using this some years ago. ISO8601 does not require milliseconds. If the other endpoint requires them, it's violating the defacto standard.

Json.NET has been using IOS8601 since version 4.5. The current one is 10.0.3. The following code :

JsonConvert.SerializeObject(DateTime.Now)

returns

"2017-09-08T19:01:55.714942+03:00"

On my machine. Notice the timezone offset. That's also part of the standard. Z means UTC.

You can specify your own time format, provided it's the correct one. In this case, it should be yyyy-MM-ddTHH:mm:ss.fffZ. Notice the fff for milliseconds and HH for 24-hour.

The following code

var settings=new JsonSerializerSettings{DateFormatString ="yyyy-MM-ddTHH:mm:ss.fffZ"};
var json=JsonConvert.SerializeObject(DateTime.Now,settings);

returns

"2017-09-08T19:04:14.480Z"

The format string does not force a timezone translation. You can tell Json.NET to treat the time as Local or Utc through the DateTimeZoneHandling setting :

var settings=new JsonSerializerSettings{
DateFormatString ="yyyy-MM-ddTH:mm:ss.fffZ",
DateTimeZoneHandling=DateTimeZoneHandling.Utc};
var json=JsonConvert.SerializeObject(DateTime.Now,settings);

Returns :

"2017-09-08T16:08:19.290Z"

UPDATE

As Matt Johnson explains, Z is just a literal, while K generates either Z or an offset, depending on the DateTimeZoneHandling setting.

The format string yyyy-MM-ddTH:mm:ss.fffK with DateTimeZoneHandling.Utc :

var settings=new JsonSerializerSettings{
DateFormatString ="yyyy-MM-ddTH:mm:ss.fffK",
DateTimeZoneHandling=DateTimeZoneHandling.Utc};
var json=JsonConvert.SerializeObject(DateTime.Now,settings);

Will return :

2017-09-11T9:10:08.293Z

Changing to DateTimeZoneHandling.Utc will return

2017-09-11T12:15:12.862+03:00

Which, by the way is the default behaviour of Json.NET, apart from the forced millisecond precision.

Finally, .NET doesn't have a Date-only type yet. DateTime is used for both dates and date+time values. You can get the date part of a DateTime with the DateTime.Date property. You can retrieve the current date with DateTime.Today.

Time of day is represented by the Timespan type. You can extract the time of day from a DateTime value with DateTime.TimeOfDay. Timespan isn't strictly a time-of-day type as it can represent more than 24 hours.

What was that yet?

Support for explicit Date, TimeOfDay is comming through the CoreFX Lab project. This contains "experimental" features that are extremely likely to appear in the .NET Runtime like UTF8 support, Date, String, Channles. Some of these already appear as separate NuGet packages.

One can use the System.Time classes already, either by copying the code or adding them through the experimental NuGet source



Related Topics



Leave a reply



Submit