Developer.

Custom Date Deserialization in ASP.NET Core Web API

This weekend I came across some oddly formatted datetimes while playing around with Twitter webhooks. When it came time to test my API endpoint to receive the Twitter event, I received a 400 response with a message indicating that one of the date fields the JSON payload could not be deserialized into a DateTimeOffset. In this post I’ll demonstrate how to write a custom converter to solve this and similar JSON deserialization problems in an ASP.NET Core Web API.

A clock

Photo by Laura Chouette on Unsplash

The Problem

Sadly, there’s not a universally accepted format for sending dates in JSON. Dates in JSON are just strings after all, and although the ISO 8601 format is the most commonly used and de facto standard, it’s far from universal.

An ISO 8601 datetime looks like this: 2018-10-10T20:19:24-00:00

Twitter formats their datetimes like this: Wed Oct 10 20:19:24 +0000 2018

I don’t know if this is a format that’s used anywhere outside of Twitter or not, but I do know that the .NET JSON deserializer doesn’t know what to do with it.

When a payload with a datetime like that is POSTed to my ASP.NET Core Web API endpoint, my API complains:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|1fb1cbcd-465ead216a3ec717.",
    "errors": {
        "$.tweet_create_events[0].created_at": [
            "The JSON value could not be converted to System.DateTimeOffset. Path: $.tweet_create_events[0].created_at | LineNumber: 5 | BytePositionInLine: 49."
        ]
    }
}

The Solution

I need to configure my API to deserialize datetimes in the Twitter format into a .NET DateTimeOffset. To do this, I’ll create a custom JsonConverter and add it to the collection of JSON converters my service wires up at start-up.

Note that I’m using the System.Text.Json serializer that was introduced with .NET Core 3.0, not the Newtonsoft.Json serializer.

First let’s create the custom converter.

TwitterDateTimeOffsetConverter

My custom converter derives from JsonConverter<DateTimeOffset>. The generic type parameter specifies the data type that we’ll be deserializing to/serializing from on the .NET side.

The converter implements two methods, Read(..) and Write(..), which deserialize from and serialize to our custom datetime format specified in the TwitterDateFormat constant.

public class TwitterDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
    private const string TwitterDateFormat = "ddd MMM dd HH:mm:ss +ffff yyyy";

    public override DateTimeOffset Read(
        ref Utf8JsonReader reader, 
        Type typeToConvert, 
        JsonSerializerOptions options) =>
        DateTimeOffset.ParseExact(reader.GetString(), TwitterDateFormat , CultureInfo.InvariantCulture);

    public override void Write(
        Utf8JsonWriter writer, 
        DateTimeOffset value, 
        JsonSerializerOptions options) =>
        writer.WriteStringValue(value.ToString(TwitterDateFormat , CultureInfo.InvariantCulture));
}

Startup Configuration

The only thing left to do now is to add my new converter to the collection of JSON converters that ASP.NET Core will use when handling requests to my API. I do that in the ConfigureServices(..) method in my Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .AddJsonOptions(opts =>
        {            
            opts.JsonSerializerOptions.Converters.Add(new TwitterDateTimeOffsetConverter());
            // Add any other converters here
            // I typically add JsonStringEnumConverter to my APIs
        });
    ...
}

I restart my service and send a test payload to my endpoint and everything is 200 - OK.

Life is good. :-)

– Jon

I’m a developer and solo SaaS founder who likes to build things and share what I learn with others. If you’re interested software development, launching things, or random early morning thoughts, consider following me on Twitter or subscribe to my newsletter.

Thanks for reading!