This document provides guidance for AI agents and developers working on the Scarlet.System.Text.Json.DateTimeConverter package.
Scarlet.System.Text.Json.DateTimeConverter is a .NET library that provides flexible custom date/time formatting for System.Text.Json serialization. It supports both reflection-based and source generator approaches, with special .NET 9+ features for enhanced source generator compatibility.
- Custom date/time format attributes (
JsonDateTimeConverterAttributefor reflection,JsonDateTimeFormatAttributefor source generators) - Source generator-compatible format converters (
JsonDateTimeFormatConverter<T>) - .NET 9+ contract customization resolver (
DateTimeConverterResolver) - Support for
DateTime,DateTimeOffset,DateOnly,TimeOnly, and nullable variants - Multi-target framework support (.NET 6, 7, 8, 9, 10)
Scarlet.System.Text.Json.DateTimeConverter/
├── README.md # User-facing documentation
├── LICENSE # MIT License
├── version.json # Nerdbank.GitVersioning configuration
├── icon.png # Package icon
├── build/ # NUKE build system
│ ├── Build.cs # Main build configuration
│ └── _build.csproj # Build project
├── src/
│ ├── Scarlet.System.Text.Json.DateTimeConverter/
│ │ ├── Converters/ # Internal converter implementations
│ │ │ ├── DateTimeConverter.cs
│ │ │ ├── DateTimeNullableConverter.cs
│ │ │ ├── DateTimeOffsetConverter.cs
│ │ │ ├── DateTimeOffsetNullableConverter.cs
│ │ │ ├── DateOnlyConverter.cs
│ │ │ ├── DateOnlyNullableConverter.cs
│ │ │ ├── TimeOnlyConverter.cs
│ │ │ └── TimeOnlyNullableConverter.cs
│ │ ├── DateTimeConverterFactoryHelper.cs
│ │ ├── DateTimeConverterResolver.cs # .NET 9+ contract customization
│ │ ├── IJsonDateTimeFormat.cs
│ │ ├── JsonDateTimeConverterAttribute.cs # For reflection-based serialization
│ │ ├── JsonDateTimeFormatAttribute.cs # For source generators (no warnings)
│ │ ├── JsonDateTimeFormatConverter.cs
│ │ └── Scarlet.System.Text.Json.DateTimeConverter.csproj
│ └── Scarlet.System.Text.Json.DateTimeConverter.Tests/
│ ├── JsonDateTimeConverterAttributeTests.cs
│ ├── JsonDateTimeFormatConverterTests.cs
│ ├── Model/ # Test models
│ │ ├── ReflectionBasedModel.cs
│ │ ├── SourceGeneratorWithConverterModel.cs
│ │ ├── SourceGeneratorWithResolverAttributeModel.cs # Uses JsonDateTimeConverter (has warnings)
│ │ ├── SourceGeneratorWithResolverFormatModel.cs # Uses JsonDateTimeFormat (no warnings)
│ │ ├── ConverterModelJsonSerializerContext.cs
│ │ └── ResolverModelJsonSerializerContext.cs
│ └── Scarlet.System.Text.Json.DateTimeConverter.Tests.csproj
└── .github/
└── workflows/ # CI/CD workflows
├── continuous.yml # Build and test on push
└── release.yml # Publish on tag
- .NET SDK 10.0 or later (for development)
- Git (for version control)
The project uses NUKE for build automation.
# Linux/macOS
./build.sh Compile
# Windows
build.cmd Compile# Clean + Restore + Build
./build.sh Clean Compile
# Or use standard dotnet commands
dotnet clean
dotnet build src/Scarlet.System.Text.Json.DateTimeConverter.sln --configuration Release./build.sh Clean Restore VerifyFormat Compile Test Pack| Target | Description |
|---|---|
Clean |
Cleans build artifacts |
Restore |
Restores NuGet packages |
VerifyFormat |
Verifies code formatting (whitespace and style) |
Compile |
Compiles the solution |
Test |
Runs all tests |
Pack |
Creates NuGet packages |
Push |
Pushes packages to NuGet.org (requires tag + secrets) |
PushGithubNuget |
Pushes packages to GitHub Packages (requires tag + secrets) |
# Using NUKE
./build.sh Test
# Using dotnet CLI
dotnet test src/Scarlet.System.Text.Json.DateTimeConverter.Tests/Scarlet.System.Text.Json.DateTimeConverter.Tests.csproj --configuration Releasedotnet test --filter "FullyQualifiedName~ReflectionBased_DateTime_WithAttribute"Tests are organized into two main test classes:
-
JsonDateTimeConverterAttributeTests.cs- Tests for
JsonDateTimeConverterAttributewith reflection-based serialization - Tests individual primitive types and complete models
- Tests for
-
JsonDateTimeFormatConverterTests.cs- Tests for
JsonDateTimeFormatConverter<T>with both reflection and source generators - Tests for
DateTimeConverterResolver(.NET 9+) - Includes tests with null values
- Tests for
Tests follow the pattern: [ApproachType]_[Scenario]_[OptionalDetails]
Examples:
ReflectionBased_DateTime_WithAttributeSourceGenerator_CompleteModel_WithFormatConverterSourceGenerator_WithResolver_WithAttribute_UsingOptions
Edit source files in src/Scarlet.System.Text.Json.DateTimeConverter/
The project enforces code formatting. Use:
# Check format
dotnet format whitespace src/Scarlet.System.Text.Json.DateTimeConverter.sln --verify-no-changes
dotnet format style src/Scarlet.System.Text.Json.DateTimeConverter.sln --verify-no-changes
# Fix format
dotnet format whitespace src/Scarlet.System.Text.Json.DateTimeConverter.sln
dotnet format style src/Scarlet.System.Text.Json.DateTimeConverter.sln./build.sh Compile TestThe project uses a standard GitHub workflow:
- Fork or create a branch
- Make changes
- Ensure tests pass
- Create pull request
Eight internal converter classes handle actual JSON serialization/deserialization:
DateTimeConverter- forDateTimeDateTimeNullableConverter- forDateTime?DateTimeOffsetConverter- forDateTimeOffsetDateTimeOffsetNullableConverter- forDateTimeOffset?DateOnlyConverter- forDateOnlyDateOnlyNullableConverter- forDateOnly?TimeOnlyConverter- forTimeOnlyTimeOnlyNullableConverter- forTimeOnly?
All use CultureInfo.InvariantCulture for consistent formatting.
JsonDateTimeConverterAttribute
[JsonDateTimeConverter("yyyy-MM-dd")]
public DateTime Date { get; set; }- Derives from
JsonConverterAttribute - Works with reflection-based serialization
- .NET 9+: Works with source generators via
DateTimeConverterResolverbut produces SYSLIB1223 warnings
JsonDateTimeFormatAttribute (.NET 9+)
[JsonDateTimeFormat("yyyy-MM-dd")]
public DateTime Date { get; set; }- Derives from
Attribute(notJsonConverterAttribute) - Designed for use with .NET 9+ source generators and
DateTimeConverterResolver - No SYSLIB1223 warnings (recommended over
JsonDateTimeConverterAttributefor source generators)
JsonDateTimeFormatConverter<T>
[JsonConverter(typeof(JsonDateTimeFormatConverter<MyFormat>))]
public DateTime Date { get; set; }JsonConverterFactoryimplementation- Compatible with source generators (all .NET versions)
- Requires
IJsonDateTimeFormatimplementation
DateTimeConverterResolver (.NET 9+)
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DateTimeConverterResolver(MyJsonContext.Default)
};- Implements
IJsonTypeInfoResolverand extendsJsonSerializerContext - Uses
JsonPropertyInfo.AttributeProvider(populated by source generators in .NET 9+) to read attributes - Supports both
JsonDateTimeFormatAttribute(no warnings) andJsonDateTimeConverterAttribute(backward compatibility) - Enables attribute syntax with source generators
DateTimeConverterFactoryHelper centralizes converter instantiation based on target type.
The library targets multiple frameworks to maximize compatibility:
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>DateTimeConverterResolver is conditionally compiled for .NET 9+ only:
#if NET9_0_OR_GREATER
public class DateTimeConverterResolver : JsonSerializerContext, IJsonTypeInfoResolver
{
// Implementation
}
#endifTests verify four distinct usage patterns:
- Reflection-based - Uses
JsonDateTimeConverterAttributewith defaultJsonSerializer - Source generator with converter - Uses
JsonDateTimeFormatConverter<T>withJsonSerializerContext - Source generator with resolver (new attribute) - Uses
JsonDateTimeFormatAttribute+DateTimeConverterResolver(.NET 9+, no warnings) - Source generator with resolver (old attribute) - Uses
JsonDateTimeConverterAttribute+DateTimeConverterResolver(.NET 9+, with SYSLIB1223 warnings for backward compatibility)
Each pattern is tested with:
- Individual types (
DateTime,DateTime?,DateTimeOffset,DateTimeOffset?,DateOnly,DateOnly?,TimeOnly,TimeOnly?) - Complete models
- Null value handling
- Create converter in
Converters/directory - Implement
JsonConverter<T>withReadandWritemethods - Register in
DateTimeConverterFactoryHelper.CreateConverter - Add tests in both test files
- Update documentation
- Update
<TargetFrameworks>in.csproj - Test all scenarios on new framework
- Update
README.mdprerequisites - Update CI/CD workflows if needed
Versioning is handled by Nerdbank.GitVersioning:
- Update
version.jsonif needed - Create a git tag:
git tag 1.2.0 - Push tag:
git push origin 1.2.0 - GitHub Actions will automatically build and publish
Problem: "Shallow clone lacks the objects required to calculate version height"
Solution:
git fetch --unshallowProblem: Format verification fails
Solution:
dotnet format whitespace src/Scarlet.System.Text.Json.DateTimeConverter.sln
dotnet format style src/Scarlet.System.Text.Json.DateTimeConverter.slnProblem: SYSLIB1223 warning in tests
Solution: This is expected for SourceGeneratorWithResolverModel as it demonstrates the problem that DateTimeConverterResolver solves.
Problem: Tests fail after renaming models
Solution: Ensure all references are updated in both test files and TestModelSourceGeneratorJsonSerializerContext.cs.
On every push:
- Restore packages
- Verify code format
- Compile all target frameworks
- Run tests
- Create NuGet packages (artifacts)
On tag push (e.g., 1.2.0):
- All CI steps
- Publish to NuGet.org
- Publish to GitHub Packages
NUGET_KEY- NuGet.org API keyGITHUB_TOKEN- Automatically provided by GitHub Actions
- Use C# 12+ features where appropriate
- Nullable reference types enabled
ImplicitUsingsenabled- Follow .editorconfig rules
- Use XML documentation for public APIs
- Keep internal converters simple and focused
- Converters use
CultureInfo.InvariantCulturefor consistent, culture-independent formatting - No reflection in hot path (converters are created once and reused)
- Source generator support ensures zero reflection overhead when using AOT