Scarlet.System.Text.Json.DateTimeConverter 1.2.0

dotnet add package Scarlet.System.Text.Json.DateTimeConverter --version 1.2.0
                    
NuGet\Install-Package Scarlet.System.Text.Json.DateTimeConverter -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Scarlet.System.Text.Json.DateTimeConverter" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Scarlet.System.Text.Json.DateTimeConverter" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Scarlet.System.Text.Json.DateTimeConverter" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Scarlet.System.Text.Json.DateTimeConverter --version 1.2.0
                    
#r "nuget: Scarlet.System.Text.Json.DateTimeConverter, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Scarlet.System.Text.Json.DateTimeConverter@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Scarlet.System.Text.Json.DateTimeConverter&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Scarlet.System.Text.Json.DateTimeConverter&version=1.2.0
                    
Install as a Cake Tool

Scarlet.System.Text.Json.DateTimeConverter

Nuget Nuget GitHub codecov

A flexible and powerful library for customizing DateTime, DateTimeOffset, DateOnly, and TimeOnly serialization in System.Text.Json, with full support for both reflection-based and source generator approaches.

Table of Contents

Overview

This package provides four ways to specify custom date formats for DateTime, DateTimeOffset, DateOnly, TimeOnly, and their nullable counterparts when serializing and deserializing JSON using System.Text.Json:

  1. JsonDateTimeConverterAttribute - Simple attribute-based approach (reflection only, or .NET 9+ with resolver but produces warnings)
  2. JsonDateTimeFormatAttribute - Clean attribute for source generators with .NET 9+ resolver (no warnings)
  3. JsonDateTimeFormatConverter<T> - Type-safe converter for source generators (all .NET versions)
  4. DateTimeConverterResolver - Contract customization for .NET 9+ source generators

Installation

dotnet add package Scarlet.System.Text.Json.DateTimeConverter

Prerequisites

  • .NET 6+ for basic functionality
  • .NET 9+ for DateTimeConverterResolver (source generator attribute support)
Target Framework Reflection + Attribute Source Generator + Converter Source Generator + Attribute + Resolver
.NET 6, 7, 8
.NET 9, 10+

Quick Start

Simplest approach (reflection-based):

public class MyModel
{
    [JsonDateTimeConverter("yyyy-MM-dd")]
    public DateTime Date { get; set; }
}

var json = JsonSerializer.Serialize(new MyModel { Date = DateTime.Now });
// Output: {"Date":"2026-01-15"}

Best for source generators (.NET 9+, no warnings):

public class MyModel
{
    [JsonDateTimeFormat("yyyy-MM-dd")]
    public DateTime Date { get; set; }
}

[JsonSerializable(typeof(MyModel))]
public partial class MyJsonContext : JsonSerializerContext { }

var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DateTimeConverterResolver(MyJsonContext.Default)
};
var json = JsonSerializer.Serialize(new MyModel { Date = DateTime.Now }, options);
// Output: {"Date":"2026-01-15"}

Usage Scenarios

Reflection-Based Serialization (.NET 6+)

Use JsonDateTimeConverterAttribute for the simplest, most readable approach with reflection-based serialization.

using Scarlet.System.Text.Json.DateTimeConverter;
using System.Text.Json;

public class Order
{
    [JsonDateTimeConverter("yyyy-MM-dd")]
    public DateTime OrderDate { get; set; }

    [JsonDateTimeConverter("yyyy-MM-ddTHH:mm:ss")]
    public DateTime? ProcessedDate { get; set; }

    [JsonDateTimeConverter("yyyy-MM-ddTHH:mm:ss.fffZ")]
    public DateTimeOffset ShippedAt { get; set; }

    [JsonDateTimeConverter("MM/dd/yyyy")]
    public DateOnly DeliveryDate { get; set; }

    [JsonDateTimeConverter("HH:mm")]
    public TimeOnly DeliveryTime { get; set; }
}

// Usage
var order = new Order
{
    OrderDate = new DateTime(2026, 1, 15),
    ProcessedDate = new DateTime(2026, 1, 15, 14, 30, 0),
    ShippedAt = DateTimeOffset.UtcNow,
    DeliveryDate = new DateOnly(2026, 1, 20),
    DeliveryTime = new TimeOnly(10, 30)
};

string json = JsonSerializer.Serialize(order);
Console.WriteLine(json);
// Output: {"OrderDate":"2026-01-15","ProcessedDate":"2026-01-15T14:30:00","ShippedAt":"2026-01-15T14:30:00.123Z","DeliveryDate":"01/20/2026","DeliveryTime":"10:30"}

var deserializedOrder = JsonSerializer.Deserialize<Order>(json);

✅ Pros:

  • Clean, readable code with attribute decoration
  • Works with all .NET versions (6+)
  • Easy to use and understand

❌ Cons:

  • Only works with reflection-based serialization
  • Produces SYSLIB1223 warning with source generators (.NET 6-8)
  • No AOT (Ahead-of-Time) compilation support

Source Generator with Format Converter (.NET 6+)

Use JsonDateTimeFormatConverter<T> for source generator compatibility across all .NET versions.

using Scarlet.System.Text.Json.DateTimeConverter;
using System.Text.Json;
using System.Text.Json.Serialization;

public class Order
{
    [JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.DateOnly>))]
    public DateTime OrderDate { get; set; }

    [JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.DateTimeSeconds>))]
    public DateTime? ProcessedDate { get; set; }

    [JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.ISO8601>))]
    public DateTimeOffset ShippedAt { get; set; }

    [JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.DateOnlySlash>))]
    public DateOnly DeliveryDate { get; set; }

    [JsonConverter(typeof(JsonDateTimeFormatConverter<DateFormats.TimeOnlyShort>))]
    public TimeOnly DeliveryTime { get; set; }
}

// Define your custom date formats
public static class DateFormats
{
    public class DateOnly : IJsonDateTimeFormat
    {
        public static string Format => "yyyy-MM-dd";
    }

    public class DateTimeSeconds : IJsonDateTimeFormat
    {
        public static string Format => "yyyy-MM-ddTHH:mm:ss";
    }

    public class ISO8601 : IJsonDateTimeFormat
    {
        public static string Format => "yyyy-MM-ddTHH:mm:ss.fffZ";
    }

    public class DateOnlySlash : IJsonDateTimeFormat
    {
        public static string Format => "MM/dd/yyyy";
    }

    public class TimeOnlyShort : IJsonDateTimeFormat
    {
        public static string Format => "HH:mm";
    }
}

// Create a JsonSerializerContext for source generation
[JsonSerializable(typeof(Order))]
[JsonSourceGenerationOptions(WriteIndented = true)]
public partial class OrderJsonContext : JsonSerializerContext { }

// Usage with source generator
var order = new Order
{
    OrderDate = new DateTime(2026, 1, 15),
    ProcessedDate = new DateTime(2026, 1, 15, 14, 30, 0),
    ShippedAt = DateTimeOffset.UtcNow,
    DeliveryDate = new DateOnly(2026, 1, 20),
    DeliveryTime = new TimeOnly(10, 30)
};

string json = JsonSerializer.Serialize(order, typeof(Order), OrderJsonContext.Default);
Console.WriteLine(json);

var deserializedOrder = (Order?)JsonSerializer.Deserialize(json, typeof(Order), OrderJsonContext.Default);

✅ Pros:

  • Works with source generators (AOT-friendly)
  • Compatible with all .NET versions (6+)
  • Type-safe format definitions

❌ Cons:

  • Requires defining a class for each date format
  • More verbose than attribute-based approach
  • Format classes add boilerplate code

Source Generator with Resolver (.NET 9+)

.NET 9+ populates JsonPropertyInfo.AttributeProvider in source generators, enabling attribute-based syntax with DateTimeConverterResolver.

Use JsonDateTimeFormatAttribute for the cleanest experience without SYSLIB1223 warnings:

using Scarlet.System.Text.Json.DateTimeConverter;
using System.Text.Json;
using System.Text.Json.Serialization;

public class Order
{
    [JsonDateTimeFormat("yyyy-MM-dd")]
    public DateTime OrderDate { get; set; }

    [JsonDateTimeFormat("yyyy-MM-ddTHH:mm:ss")]
    public DateTime? ProcessedDate { get; set; }

    [JsonDateTimeFormat("yyyy-MM-ddTHH:mm:ss.fffZ")]
    public DateTimeOffset ShippedAt { get; set; }

    [JsonDateTimeFormat("MM/dd/yyyy")]
    public DateOnly DeliveryDate { get; set; }

    [JsonDateTimeFormat("HH:mm")]
    public TimeOnly DeliveryTime { get; set; }
}

[JsonSerializable(typeof(Order))]
[JsonSourceGenerationOptions(WriteIndented = true)]
public partial class OrderJsonContext : JsonSerializerContext { }

// Usage - Method 1: With JsonSerializerOptions
var options = new JsonSerializerOptions
{
    WriteIndented = true,
    TypeInfoResolver = new DateTimeConverterResolver(OrderJsonContext.Default)
};

string json = JsonSerializer.Serialize(order, options);
var deserializedOrder = JsonSerializer.Deserialize<Order>(json, options);

// Usage - Method 2: With Context directly
var resolver = new DateTimeConverterResolver(OrderJsonContext.Default);
string json = JsonSerializer.Serialize(order, typeof(Order), resolver);
var deserializedOrder = (Order?)JsonSerializer.Deserialize(json, typeof(Order), resolver);

✅ Pros:

  • Clean attribute syntax with source generators
  • AOT-friendly
  • No SYSLIB1223 warnings
  • Best of both worlds: readability + performance

❌ Cons:

  • Requires .NET 9+
  • Slightly more setup (need to wrap context with resolver)
Option B: JsonDateTimeConverterAttribute (Backward Compatible - Has Warnings)

You can also use JsonDateTimeConverterAttribute (for backward compatibility), but it will produce SYSLIB1223 warnings:

public class Order
{
    [JsonDateTimeConverter("yyyy-MM-dd")]  // ⚠️ Produces SYSLIB1223 warning
    public DateTime OrderDate { get; set; }
}

The resolver still works, but the source generator will emit warnings because JsonDateTimeConverterAttribute derives from JsonConverterAttribute.

✅ Pros:

  • Works with existing code using JsonDateTimeConverterAttribute
  • Backward compatible

❌ Cons:

  • Produces SYSLIB1223 warnings during build
  • May confuse users about the warnings

Available Components

JsonDateTimeConverterAttribute

A JsonConverterAttribute-derived attribute for specifying date formats directly on properties.

[JsonDateTimeConverter("yyyy-MM-dd")]
public DateTime Date { get; set; }

When to use: Reflection-based serialization. Can also be used with .NET 9+ DateTimeConverterResolver but produces SYSLIB1223 warnings.


JsonDateTimeFormatAttribute (.NET 9+)

A simple Attribute-derived attribute for specifying date formats with source generators (no warnings).

[JsonDateTimeFormat("yyyy-MM-dd")]
public DateTime Date { get; set; }

When to use: .NET 9+ source generators with DateTimeConverterResolver (recommended, no warnings).


JsonDateTimeFormatConverter<T>

A JsonConverterFactory that uses IJsonDateTimeFormat implementations to define formats.

public class MyFormat : IJsonDateTimeFormat
{
    public static string Format => "yyyy-MM-dd";
}

[JsonConverter(typeof(JsonDateTimeFormatConverter<MyFormat>))]
public DateTime Date { get; set; }

When to use: Source generators on any .NET version (6+).


DateTimeConverterResolver (.NET 9+)

A JsonSerializerContext and IJsonTypeInfoResolver that enables attribute-based date formatting with source generators by using contract customization.

var resolver = new DateTimeConverterResolver(MyJsonContext.Default);
var options = new JsonSerializerOptions { TypeInfoResolver = resolver };

When to use: .NET 9+ source generators with JsonDateTimeFormatAttribute or JsonDateTimeConverterAttribute.


When to Use What

Scenario Recommended Approach
Reflection-based, any .NET version JsonDateTimeConverterAttribute
Source generator, .NET 6-8 JsonDateTimeFormatConverter<T>
Source generator, .NET 9+ (no warnings) JsonDateTimeFormatAttribute + DateTimeConverterResolver
Source generator, .NET 9+ (backward compat) JsonDateTimeConverterAttribute + DateTimeConverterResolver (⚠️ warnings)
Need reusable formats across many properties JsonDateTimeFormatConverter<T> (define format class once)
Prototyping/simple projects JsonDateTimeConverterAttribute (simplest)
AOT compilation JsonDateTimeFormatConverter<T> or .NET 9+ resolver with attributes

Quirks and Limitations

Source Generator Limitations (.NET 6-8)

JsonDateTimeConverterAttribute produces SYSLIB1223 warning with source generators in .NET 6-8:

"Attributes deriving from JsonConverterAttribute are not supported by the source generator."

Solution: Use JsonDateTimeFormatConverter<T> instead, or upgrade to .NET 9+ and use JsonDateTimeFormatAttribute with DateTimeConverterResolver (no warnings).


SYSLIB1223 Warning with JsonDateTimeConverterAttribute (.NET 9+ Source Generators)

When using JsonDateTimeConverterAttribute with source generators in .NET 9+, you'll get SYSLIB1223 warnings:

"Attributes deriving from JsonConverterAttribute are not supported by the source generator."

This happens because JsonDateTimeConverterAttribute derives from JsonConverterAttribute. The code still works with DateTimeConverterResolver, but the warnings may be confusing.

Solution: Use JsonDateTimeFormatAttribute instead, which derives only from Attribute and produces no warnings:

// ❌ Produces warnings (but still works)
[JsonDateTimeConverter("yyyy-MM-dd")]
public DateTime Date { get; set; }

// ✅ No warnings
[JsonDateTimeFormat("yyyy-MM-dd")]
public DateTime Date { get; set; }

Format Class Per Format

With JsonDateTimeFormatConverter<T>, you need one class per unique format:

public class Format1 : IJsonDateTimeFormat { public static string Format => "yyyy-MM-dd"; }
public class Format2 : IJsonDateTimeFormat { public static string Format => "yyyy-MM-ddTHH:mm:ss"; }

This is a limitation of source generators not supporting constructor parameters or static analyzer tricks.


.NET 9+ Resolver Requirement

DateTimeConverterResolver only works on .NET 9+ because, while JsonPropertyInfo.AttributeProvider exists in .NET 7-8, it is not populated by source generators until .NET 9+. See runtime#100095 and runtime#102078 for details.


Null Handling

Nullable types (DateTime?, DateTimeOffset?) write null in JSON when the value is null:

{
  "NullableDate": null
}

This matches standard System.Text.Json behavior.


Supported Types

  • DateTime
  • DateTime?
  • DateTimeOffset
  • DateTimeOffset?
  • DateOnly
  • DateOnly?
  • TimeOnly
  • TimeOnly?

All types support any valid .NET date and time format string.


License

This project is licensed under the MIT License - see the LICENSE file for details.


Contributing

Contributions are welcome! Please open an issue or pull request on GitHub.


Support

If you encounter any issues or have questions, please open an issue on the GitHub repository.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 96 1/15/2026
1.1.0 561 10/29/2024
1.0.0 191 10/28/2024