Skip to content

Commit

Permalink
Add dictionary key support for STJ
Browse files Browse the repository at this point in the history
  • Loading branch information
buvinghausen committed Feb 15, 2024
1 parent 0d01b03 commit a636c3b
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

<ItemGroup>
<ProjectReference Include="..\NodaTime.Serialization.JsonNet\NodaTime.Serialization.JsonNet.csproj" />
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>
</Project>
38 changes: 37 additions & 1 deletion src/NodaTime.Serialization.SystemTextJson/NodaConverterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ public override T Read(ref Utf8JsonReader reader, Type objectType, JsonSerialize
}
}

#if NET6_0_OR_GREATER
/// <summary>
/// Converts the JSON stored in a reader into the relevant Noda Time type.
/// </summary>
/// <param name="reader">The json reader to read data from.</param>
/// <param name="typeToConvert">The type to convert the JSON to.</param>
/// <param name="options">A serializer options to use for any embedded deserialization.</param>
/// <exception cref="InvalidNodaDataException">The JSON was invalid for this converter.</exception>
/// <returns>The deserialized value.</returns>
public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
try
{
// Delegate to the concrete subclass.
return ReadJsonImpl(ref reader, options);
}
catch (Exception ex)
{
throw new JsonException($"Cannot convert value to {typeToConvert}", ex);
}
}
#endif

/// <summary>
/// Implemented by concrete subclasses, this performs the final conversion from a non-null JSON value to
/// a value of type T.
Expand All @@ -86,13 +110,25 @@ public override T Read(ref Utf8JsonReader reader, Type objectType, JsonSerialize
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
WriteJsonImpl(writer, value, options);

#if NET6_0_OR_GREATER
/// <summary>
/// Writes the value as a string to a Utf8JsonWriter.
/// </summary>
/// <param name="writer">The writer to write the JSON to.</param>
/// <param name="value">The value to write.</param>
/// <param name="options">The serializer options to use for any embedded serialization.</param>
public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
WriteJsonImpl(writer, value, options, true);
#endif

/// <summary>
/// Implemented by concrete subclasses, this performs the final write operation for a non-null value of type T
/// to JSON.
/// </summary>
/// <param name="writer">The writer to write JSON data to</param>
/// <param name="value">The value to serializer</param>
/// <param name="options">The serializer options to use for nested serialization</param>
protected abstract void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options);
/// <param name="isProperty">Boolean flag to determine which writer method to invoke</param>
protected abstract void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options, bool isProperty = false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ protected override DateInterval ReadJsonImpl(ref Utf8JsonReader reader, JsonSeri
/// <param name="writer">The writer to write JSON to</param>
/// <param name="value">The date interval to serialize</param>
/// <param name="options">The serializer options for embedded serialization.</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options)
/// <param name="isProperty">DateInterval cannot be converted to a scalar string value as such it is not a valid choice for a dictionary key</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options, bool isProperty = false)
{
writer.WriteStartObject();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,16 @@ protected override DateTimeZone ReadJsonImpl(ref Utf8JsonReader reader, JsonSeri
/// <param name="writer">The writer to write JSON data to.</param>
/// <param name="value">The value to serializer.</param>
/// <param name="options">The serialization options to use for nested serialization.</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, DateTimeZone value, JsonSerializerOptions options) =>
writer.WriteStringValue(value.Id);
/// <param name="isProperty">Conditional to indicate which function to invoke on the writer</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, DateTimeZone value, JsonSerializerOptions options,
bool isProperty = false)
{
#if NET6_0_OR_GREATER
if (isProperty)
writer.WritePropertyName(value.Id);
else
#endif
writer.WriteStringValue(value.Id);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ protected override Interval ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializ
/// <param name="writer">The writer to write JSON to</param>
/// <param name="value">The interval to serialize</param>
/// <param name="options">The serializer options for embedded serialization.</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options)
/// <param name="isProperty">Interval cannot be converted to a scalar string value as such it is not a valid choice for a dictionary key</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options, bool isProperty = false)
{
writer.WriteStartObject();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,17 @@ protected override DateInterval ReadJsonImpl(ref Utf8JsonReader reader, JsonSeri
/// <param name="writer">The writer to write JSON to</param>
/// <param name="value">The date interval to serialize</param>
/// <param name="options">The serializer options for embedded serialization.</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options)
/// <param name="isProperty">Conditional to indicate which function to invoke on the writer</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options, bool isProperty = false)
{
var pattern = LocalDatePattern.Iso;
string text = pattern.Format(value.Start) + "/" + pattern.Format(value.End);
writer.WriteStringValue(text);
var text = $"{pattern.Format(value.Start)}/{pattern.Format(value.End)}";
#if NET6_0_OR_GREATER
if (isProperty)
writer.WritePropertyName(text);
else
#endif
writer.WriteStringValue(text);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,17 @@ protected override Interval ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializ
/// <param name="writer">The writer to write JSON to</param>
/// <param name="value">The interval to serialize</param>
/// <param name="options">The serializer options for embedded serialization.</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options)
/// <param name="isProperty">Interval cannot be converted to a scalar string value as such it is not a valid choice for a dictionary key</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options, bool isProperty = false)
{
var pattern = InstantPattern.ExtendedIso;
string text = (value.HasStart ? pattern.Format(value.Start) : "") + "/" + (value.HasEnd ? pattern.Format(value.End) : "");
writer.WriteStringValue(text);
var text = $"{(value.HasStart ? pattern.Format(value.Start) : "")}/{(value.HasEnd ? pattern.Format(value.End) : "")}";
#if NET6_0_OR_GREATER
if (isProperty)
writer.WritePropertyName(text);
else
#endif
writer.WriteStringValue(text);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,17 @@ protected override T ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptio
/// <param name="writer">The writer to write JSON data to</param>
/// <param name="value">The value to serializer</param>
/// <param name="options">The serializer options to use for nested serialization</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
/// <param name="isProperty">Conditional to indicate which function to invoke on the writer</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options, bool isProperty = false)
{
validator?.Invoke(value);
writer.WriteStringValue(pattern.Format(value));
var text = pattern.Format(value);
#if NET6_0_OR_GREATER
if (isProperty)
writer.WritePropertyName(text);
else
#endif
writer.WriteStringValue(text);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Provides serialization support between Noda Time and System.Text.Json</Description>
<Version>1.1.2</Version>
<TargetFrameworks>netstandard2.0;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6.0</TargetFrameworks>
<PackageTags>nodatime;json</PackageTags>
</PropertyGroup>

Expand All @@ -13,6 +13,7 @@

<ItemGroup>
<PackageReference Include="NodaTime" Version="[3.0.0,4.0.0)" />
<PackageReference Include="System.Text.Json" Version="5.0.2" />
<!-- Only reference the shim on netstandard -->
<PackageReference Include="System.Text.Json" Version="5.0.2" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ protected override int ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOpt
return int.Parse(reader.GetString());
}

protected override void WriteJsonImpl(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
protected override void WriteJsonImpl(Utf8JsonWriter writer, int value, JsonSerializerOptions options, bool isProperty = false)
{
writer.WriteStringValue(value.ToString());
}
Expand All @@ -88,7 +88,7 @@ protected override string ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializer
return reader.GetString();
}

protected override void WriteJsonImpl(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
protected override void WriteJsonImpl(Utf8JsonWriter writer, string value, JsonSerializerOptions options, bool isProperty = false)
{
writer.WriteStringValue(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// as found in the LICENSE.txt file.

using System;
using System.Collections.Generic;
using System.Text.Json;
using NodaTime.Serialization.SystemTextJson;
using NUnit.Framework;
Expand Down Expand Up @@ -39,7 +40,7 @@ public void InstantConverter_EquivalentToIsoDateTimeConverter()
var jsonDateTime = JsonSerializer.Serialize(dateTime);
var jsonInstant = JsonSerializer.Serialize(instant, new JsonSerializerOptions
{
Converters = {NodaConverters.InstantConverter},
Converters = { NodaConverters.InstantConverter },
WriteIndented = false
});
Assert.AreEqual(jsonDateTime, jsonInstant);
Expand All @@ -53,6 +54,39 @@ public void LocalDateConverter()
AssertConversions(value, json, NodaConverters.LocalDateConverter);
}

[Test]
public void LocalDateDictionaryKeySerialize()
{
const string expected = "{\"2012-12-21\":\"Mayan Calendar\",\"2012-12-22\":\"We Survived\"}";
var actual = JsonSerializer.Serialize(new Dictionary<LocalDate, string>
{
[new LocalDate(2012, 12, 21, CalendarSystem.Iso)] = "Mayan Calendar",
[new LocalDate(2012, 12, 22, CalendarSystem.Iso)] = "We Survived"
}, new JsonSerializerOptions
{
WriteIndented = false,
Converters = { NodaConverters.LocalDateConverter }
});
Assert.AreEqual(expected, actual);
}

[Test]
public void LocalDateDictionaryKeyDeserialize()
{
var expected = new Dictionary<LocalDate, string>
{
[new LocalDate(2012, 12, 21, CalendarSystem.Iso)] = "Mayan Calendar",
[new LocalDate(2012, 12, 22, CalendarSystem.Iso)] = "We Survived"
};
var actual = JsonSerializer.Deserialize<Dictionary<LocalDate, string>>(
"{\"2012-12-21\":\"Mayan Calendar\",\"2012-12-22\":\"We Survived\"}",
new JsonSerializerOptions
{
Converters = { NodaConverters.LocalDateConverter }
});
Assert.AreEqual(expected, actual);
}

[Test]
public void LocalDateConverter_SerializeNonIso_Throws()
{
Expand Down Expand Up @@ -83,7 +117,7 @@ public void LocalDateTimeConverter_EquivalentToIsoDateTimeConverter()
var jsonDateTime = JsonSerializer.Serialize(dateTime);
var jsonLocalDateTime = JsonSerializer.Serialize(localDateTime, new JsonSerializerOptions
{
Converters = {NodaConverters.LocalDateTimeConverter},
Converters = { NodaConverters.LocalDateTimeConverter },
WriteIndented = false
});

Expand All @@ -97,7 +131,7 @@ public void LocalDateTimeConverter_SerializeNonIso_Throws()

Assert.Throws<ArgumentException>(() => JsonSerializer.Serialize(localDateTime, new JsonSerializerOptions
{
Converters = {NodaConverters.LocalDateTimeConverter},
Converters = { NodaConverters.LocalDateTimeConverter },
WriteIndented = false
}));
}
Expand Down Expand Up @@ -125,7 +159,7 @@ public void NormalizingIsoPeriodConverter_RequiresNormalization()
var period = Period.FromDays(2) + Period.FromHours(3) + Period.FromMinutes(90);
var json = JsonSerializer.Serialize(period, new JsonSerializerOptions
{
Converters = {NodaConverters.NormalizingIsoPeriodConverter},
Converters = { NodaConverters.NormalizingIsoPeriodConverter },
WriteIndented = false
});
string expectedJson = "\"P2DT4H30M\"";
Expand Down Expand Up @@ -235,7 +269,7 @@ public void RoundtripDuration_MinAndMaxValues()
[Test]
public void Duration_ParsePartialFractionalSecondsWithTrailingZeroes()
{
var parsed = JsonSerializer.Deserialize<Duration>("\"25:10:00.1234000\"", new JsonSerializerOptions{Converters = {NodaConverters.DurationConverter }});
var parsed = JsonSerializer.Deserialize<Duration>("\"25:10:00.1234000\"", new JsonSerializerOptions { Converters = { NodaConverters.DurationConverter } });
Assert.AreEqual(Duration.FromHours(25) + Duration.FromMinutes(10) + Duration.FromTicks(1234000), parsed);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,31 @@

using System.Text.Json;
using NodaTime.Serialization.SystemTextJson;
using NodaTime.Utility;
using NUnit.Framework;
using static NodaTime.Serialization.Test.SystemText.TestHelper;

namespace NodaTime.Serialization.Test.SystemText
{
public class NodaDateIntervalConverterTest
{
private readonly JsonSerializerOptions options = new JsonSerializerOptions
private readonly JsonSerializerOptions options = new()
{
Converters = { NodaConverters.DateIntervalConverter, NodaConverters.LocalDateConverter },
};

private readonly JsonSerializerOptions optionsCaseInsensitive = new JsonSerializerOptions
private readonly JsonSerializerOptions optionsCaseInsensitive = new()
{
Converters = { NodaConverters.DateIntervalConverter, NodaConverters.LocalDateConverter },
PropertyNameCaseInsensitive = true,
};

private readonly JsonSerializerOptions optionsCamelCase = new JsonSerializerOptions
private readonly JsonSerializerOptions optionsCamelCase = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { NodaConverters.DateIntervalConverter, NodaConverters.LocalDateConverter },
};

private readonly JsonSerializerOptions optionsCamelCaseCaseInsensitive = new JsonSerializerOptions
private readonly JsonSerializerOptions optionsCamelCaseCaseInsensitive = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { NodaConverters.DateIntervalConverter, NodaConverters.LocalDateConverter },
Expand Down

0 comments on commit a636c3b

Please sign in to comment.