Skip to content

Commit

Permalink
Add a JsonConverterFactory
Browse files Browse the repository at this point in the history
This refactors NodaTimeDefaultJsonConverterAttribute to use the converter dictionary which is now in NodaTimeDefaultJsonConverterFactory.

Fixes #97.
  • Loading branch information
jskeet committed Aug 6, 2023
1 parent e9c575e commit 11b72af
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// as found in the LICENSE.txt file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand All @@ -21,43 +19,14 @@ namespace NodaTime.Serialization.SystemTextJson;
/// </remarks>
public sealed class NodaTimeDefaultJsonConverterAttribute : JsonConverterAttribute
{
private static readonly Dictionary<Type, JsonConverter> converters;

/// <summary>
/// Constructs an instance of the attribute.
/// </summary>
public NodaTimeDefaultJsonConverterAttribute()
{
}

static NodaTimeDefaultJsonConverterAttribute()
{
converters = new()
{
{ typeof(AnnualDate), NodaConverters.AnnualDateConverter },
{ typeof(DateInterval), NodaConverters.DateIntervalConverter },
{ typeof(DateTimeZone), NodaConverters.CreateDateTimeZoneConverter(DateTimeZoneProviders.Tzdb) },
{ typeof(Duration), NodaConverters.DurationConverter },
{ typeof(Instant), NodaConverters.InstantConverter },
{ typeof(Interval), NodaConverters.IntervalConverter },
{ typeof(LocalDate), NodaConverters.LocalDateConverter },
{ typeof(LocalDateTime), NodaConverters.LocalDateTimeConverter },
{ typeof(LocalTime), NodaConverters.LocalTimeConverter },
{ typeof(Offset), NodaConverters.OffsetConverter },
{ typeof(OffsetDate), NodaConverters.OffsetDateConverter },
{ typeof(OffsetDateTime), NodaConverters.OffsetDateTimeConverter },
{ typeof(OffsetTime), NodaConverters.OffsetTimeConverter },
{ typeof(Period), NodaConverters.RoundtripPeriodConverter },
{ typeof(ZonedDateTime), NodaConverters.CreateZonedDateTimeConverter(DateTimeZoneProviders.Tzdb) }
};
// Use the same converter for Nullable<T> as T.
foreach (var entry in converters.Where(pair => pair.Key.IsValueType).ToList())
{
converters[typeof(Nullable<>).MakeGenericType(entry.Key)] = entry.Value;
}
}

/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert) =>
converters.TryGetValue(typeToConvert, out var converter) ? converter : null;
NodaTimeDefaultJsonConverterFactory.Converters.TryGetValue(typeToConvert, out var converter) ? converter : null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2023 The Noda Time Authors. All rights reserved.
// Use of this source code is governed by the Apache License 2.0,
// as found in the LICENSE.txt file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace NodaTime.Serialization.SystemTextJson;

/// <summary>
/// Provides JSON default converters for Noda Time types, as if using serializer
/// options configured by <see cref="Extensions.ConfigureForNodaTime(JsonSerializerOptions, IDateTimeZoneProvider)"/>
/// with a provider of <see cref="DateTimeZoneProviders.Tzdb"/>.
/// </summary>
/// <remarks>
/// This is a factory equalivalent of <see cref="NodaTimeDefaultJsonConverterAttribute"/>.
/// </remarks>
public sealed class NodaTimeDefaultJsonConverterFactory : JsonConverterFactory
{
/// <summary>
/// A dictionary of default converters, keyed by type. This includes nullable types.
/// This dictionary is internal and must not be mutated after it is initialized
/// in the static constructor.
/// </summary>
internal static Dictionary<Type, JsonConverter> Converters { get; }

/// <summary>
/// Constructs an instance of the factory.
/// </summary>
public NodaTimeDefaultJsonConverterFactory()
{
}

static NodaTimeDefaultJsonConverterFactory()
{
Converters = new()
{
{ typeof(AnnualDate), NodaConverters.AnnualDateConverter },
{ typeof(DateInterval), NodaConverters.DateIntervalConverter },
{ typeof(DateTimeZone), NodaConverters.CreateDateTimeZoneConverter(DateTimeZoneProviders.Tzdb) },
{ typeof(Duration), NodaConverters.DurationConverter },
{ typeof(Instant), NodaConverters.InstantConverter },
{ typeof(Interval), NodaConverters.IntervalConverter },
{ typeof(LocalDate), NodaConverters.LocalDateConverter },
{ typeof(LocalDateTime), NodaConverters.LocalDateTimeConverter },
{ typeof(LocalTime), NodaConverters.LocalTimeConverter },
{ typeof(Offset), NodaConverters.OffsetConverter },
{ typeof(OffsetDate), NodaConverters.OffsetDateConverter },
{ typeof(OffsetDateTime), NodaConverters.OffsetDateTimeConverter },
{ typeof(OffsetTime), NodaConverters.OffsetTimeConverter },
{ typeof(Period), NodaConverters.RoundtripPeriodConverter },
{ typeof(ZonedDateTime), NodaConverters.CreateZonedDateTimeConverter(DateTimeZoneProviders.Tzdb) }
};
// Use the same converter for Nullable<T> as T.
foreach (var entry in Converters.Where(pair => pair.Key.IsValueType).ToList())
{
Converters[typeof(Nullable<>).MakeGenericType(entry.Key)] = entry.Value;
}
}

/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => Converters.ContainsKey(typeToConvert);

/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
Converters.TryGetValue(typeToConvert, out var converter) ? converter : null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2023 The Noda Time Authors. All rights reserved.
// Use of this source code is governed by the Apache License 2.0,
// as found in the LICENSE.txt file.

using NodaTime.Serialization.SystemTextJson;
using NUnit.Framework;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace NodaTime.Serialization.Test.SystemTextJson;

public partial class NodaTimeDefaultJsonConverterFactoryTest
{
// See https://github.com/nodatime/nodatime.serialization/issues/97
[Test]
public void SourceGeneration()
{
var sample = new SampleData { Foo = Instant.FromUtc(2023, 8, 6, 12, 40, 12) };
byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(sample, SampleJsonContext.Default.SampleData);
string actual = Encoding.UTF8.GetString(utf8Json);
string expected = "{\"Foo\":\"2023-08-06T12:40:12Z\"}";
Assert.AreEqual(expected, actual);
}

public class SampleData
{
[JsonConverter(typeof(NodaTimeDefaultJsonConverterFactory))]
public Instant Foo { get; set; }
}

[JsonSerializable(typeof(SampleData))]
public partial class SampleJsonContext : JsonSerializerContext
{
}
}

0 comments on commit 11b72af

Please sign in to comment.