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:
- Serializing an instance of
Class2
to a JSON string using specificDateTime
-related serialization settings. - Deserializing to a
JToken
hierarchy without using those settings. - (Making additional modifications to the hierarchy - not shown.)
- Serializing the
JToken
hierarchy to a final string (viajson.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
Wpf Databinding to Interface and Not Actual Object - Casting Possible
Entity Framework Thread Safety
Iterating Through All Nodes in Xml File
C# Static Variables - Scope and Persistence
JSON.Net Cast Error When Serializing Mongo Objectid
.Net Framework 3.5 and Tls 1.2
Best Method to Obfuscate or Secure .Net Assemblies
How to Bulk Update Records in Entity Framework
Save Settings in a .Net Winforms Application
How to Ensure All Data Has Been Physically Written to Disk
Better Way to Trigger Onpropertychanged
Why Won't Control Update/Refresh Mid-Process
Control Cannot Fall Through from One Case Label
ASP.NET Core 3.0 System.Text.JSON Camel Case Serialization
405 Method Not Allowed Web API