Part of the series: Fun with System.Text.Json
I was working on porting some code to .NET Core that has a batch process running at a configurable time of day. Naturally, the time is configurable, so I put a TimeSpan on my options object and added a string time in my JSON settings “02:00:00” for 2am default. Sure enough, Exception is thrown trying to deserialize the TimeSpan.
Surprising that System.Text.Json supports DateTimes and not TimeSpans, right? The good news is it looks like we might get this in .NET Core 5.
For cases like this System.Text.Json gives us a nice custom conversion mechanism (JsonConverter) so all we have to do is spin up a converter to handle TimeSpan. Here’s mine: JsonTimeSpanConverter (available as part of Macross.Json.Extensions).
Usage is simple:
public class TestClass { [JsonConverter(typeof(JsonTimeSpanConverter))] public TimeSpan TimeSpan { get; set; } [JsonConverter(typeof(JsonTimeSpanConverter))] public TimeSpan? NullableTimeSpan { get; set; } }
You can also add an instance to your JsonSerializerOptions.Converters collection if you have some kind of static options instance dialed in. If your choice is between using the attributes and newing up an options instance each call to the engine, go with the attributes, friends. System.Text.Json builds a cache of all your types at runtime so the attribute route will be faster than allocating new options each time you need to do work.
This is all very simple. You can stop reading here if all you need is to get your TimeSpans working.
But the story for me was far more interesting. If you look at the code to the converter, mine is actually a JsonConverterFactory. The reason for that is I wanted to support TimeSpan and Nullable<TimeSpan>. That is actually really hard to do with C#. There’s no constraint that you can use to make your life easier. If you can figure out how, and show me, I’ll buy you lunch.
So the implementation is actually quite nasty. There are two basically identical “private class” types for doing the conversion, just to satisfy C# and the JSON engine. What is interesting is you have to do all that heavy lifting to tell System.Text.Json you support Nullable, and make it happy when it is building its type cache, but when it comes down to the actual serializing and deserializing at runtime… it never actually calls our converter for null.
Wouldn’t it be great if for value types the engine just took care of the Nullable case completely and saved converter authors the trouble? I went digging into System.Text.Json and as it turns out, it was trying to do that, but there’s a bug. I submitted a fix to try and get it resolved.
Update: TimeSpan serialization didn’t make .NET 5, but it should be part of .NET 6.