.Net Core 3.0 TimeSpan deserialization error - Fixed in .Net 5.0

My solution is to use custom converter, but with explicitly specified standard non culture-sensitive TimeSpan format specifier .

public class JsonTimeSpanConverter : JsonConverter<TimeSpan>
{
    public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return TimeSpan.ParseExact(reader.GetString(), "c", CultureInfo.InvariantCulture);
    }

    public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("c", CultureInfo.InvariantCulture));
    }
}

Then register it in the Startup for the HostBuilder:

public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        ...
        services
            ...
            .AddJsonOptions(opts =>
            {
                opts.JsonSerializerOptions.Converters.Add(new JsonTimeSpanConverter());                    
            });
        ...
    }
}

The REST API service shouldn't produce such a JSON string. I'd bet that previous versions returned 00:0:00 instead of all the properties of a TimeSpan object.

The reason for this is that .NET Core 3.0 replaced JSON.NET with a new, bult-in JSON serializer, System.Text.Json. This serializer doesn't support TimeSpan. The new serializer is faster, doesn't allocate in most cases, but doesn't cover all the cases JSON.NET did.

In any case, there's no standard way to represent dates or periods in JSON. Even the ISO8601 format is a convention, not part of the standard itself. JSON.NET uses a readable format (23:00:00), but ISO8601's duration format would look like P23DT23H (23 days, 23 hours) or P4Y (4 years).

One solution is to go back to JSON.NET. The steps are described in the docs:

  • Add a package reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  • Update Startup.ConfigureServices to call AddNewtonsoftJson.

services.AddMvc()
    .AddNewtonsoftJson();

Another option is to use a custom converter for that type, eg :

public class TimeSpanToStringConverter : JsonConverter<TimeSpan>
{
    public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var value=reader.GetString();
        return TimeSpan.Parse(value);
    }

    public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

And register it in Startup.ConfigureServices with AddJsonOptions, eg :

services.AddControllers()                    
        .AddJsonOptions(options=>
            options.JsonSerializerOptions.Converters.Add(new TimeSpanToStringConverter()));