NodaTime serialization output in Azure Functions

February 24, 2020

The interaction between NodaTime and JsonNET becomes non-trivial when working in Azure Functions. Both tools make assumptions around the configuration of the system that don’t hold within the more limited world of Azure Functions.

In a typical netcore system, all that’s needed is the right nuget packages and for them to be connected via the services builder.

services.AddJsonFormatters(
  settings => settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)
);

In Azure Functions, the startup builder doesn’t have the same features that are present in AspNet core. We could use the result builder by supplying serializer settings when creating the ActionResult object. However, this introduces inconsistency bugs since it’s entirely reliant on the developer remembering to supply the settings.

The route I went to get consistency, was to use attribute decoration on the contracts class. Attributes solve the consistency issue and sidestep the lack of access to the global JSON serializer settings. The only problem is that the attributes need types and NodaTime only provides instances of converters.

That problem is easy to solve with an abstract base class to provide the delegation to the NodaTime instance.

public abstract class TypedNodaJsonConverter : JsonConverter
{
    protected JsonConverter NodaConverter { get; set; }

    public override bool CanConvert(Type objectType)
    {
        return NodaConverter.CanConvert(objectType);
    }

    public override object ReadJson(
        JsonReader reader,
        Type objectType,
        object existingValue,
        JsonSerializer serializer)
    {
        return NodaConverter.ReadJson(
            reader,
            objectType,
            existingValue,
            serializer);
    }

    public override void WriteJson(
        JsonWriter writer,
        object value,
        JsonSerializer serializer)
    {
        NodaConverter.WriteJson(writer, value, serializer);
    }
}

A concrete class that specifies which NodaTime instance to use:

public class LocalDateConverter : TypedNodaJsonConverter
{
    public LocalDateConverter()
    {
        NodaConverter = NodaConverters.LocalDateConverter;
    }
}

Allowing me to use the attribute:

[JsonProperty]
[JsonConverter(typeof(LocalDateConverter))]
public LocalDate LocalEndDate { get; protected set; }