Skip to content

Commit

Permalink
(#115) Specify culture for JSON serialization. (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianhall authored Sep 27, 2024
1 parent 9780f81 commit ad522d6
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand All @@ -14,12 +15,13 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Json;
public class DateTimeConverter : JsonConverter<DateTime>
{
private const string format = "yyyy-MM-dd'T'HH:mm:ss.fffK";
private static readonly CultureInfo culture = new("en-US");

/// <inheritdoc />
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTime.Parse(reader.GetString() ?? string.Empty);

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().ToString(format));
=> writer.WriteStringValue(value.ToUniversalTime().ToString(format, culture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand All @@ -14,12 +15,13 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Json;
public class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
private const string format = "yyyy-MM-dd'T'HH:mm:ss.fffK";
private static readonly CultureInfo culture = new("en-US");

/// <inheritdoc />
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTimeOffset.Parse(reader.GetString() ?? string.Empty);

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().UtcDateTime.ToString(format));
=> writer.WriteStringValue(value.ToUniversalTime().UtcDateTime.ToString(format, culture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand All @@ -14,12 +15,13 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Json;
public class TimeOnlyConverter : JsonConverter<TimeOnly>
{
private const string format = "HH:mm:ss.fff";
private static readonly CultureInfo culture = new("en-US");

/// <inheritdoc />
public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> TimeOnly.Parse(reader.GetString() ?? string.Empty);

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString(format));
=> writer.WriteStringValue(value.ToString(format, culture));
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ public class GenericAuthenticationProvider_Tests
};
#endregion

[Fact]
public void Ctor_WhiteSpace_Header_Throws()
{
Action act = () => _ = new GenericAuthenticationProvider(_ => Task.FromResult(ValidAuthenticationToken), "X-ZUMO-AUTH", " ");
act.Should().Throw<ArgumentException>();
}

[Fact]
public void Ctor_NullTokenRequestor_Throws()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,48 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Test.Json;
[ExcludeFromCodeCoverage]
public class DateTimeConverter_Tests : SerializerTests
{
[Fact]
public void Converter_ReadsJson()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_ReadsJson(string culture)
{
string json = """{"updatedAt":"2021-08-21T12:30:15.123+00:00"}""";
DateTime value = DateTime.Parse("2021-08-21T12:30:15.123+00:00");
DateTime value = new(2021, 8, 21, 12, 30, 15, 123, DateTimeKind.Utc);

Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.ToFileTime().Should().Be(value.ToFileTime());
TestWithCulture(culture, () =>
{
Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.ToFileTime().Should().Be(value.ToFileTime());
});
}

[Fact]
public void Converter_WritesJson()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson(string culture)
{
string json = """{"updatedAt":"2021-08-21T12:30:15.123Z"}""";
Entity entity = new() { UpdatedAt = DateTime.Parse("2021-08-21T12:30:15.1234567+00:00") };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
DateTime value = new(2021, 8, 21, 12, 30, 15, 123, 456, DateTimeKind.Utc);

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
public void Converter_WritesJson_WithTimeZone()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson_WithTimeZone(string culture)
{
string json = """{"updatedAt":"2021-08-21T12:30:15.123Z"}""";
Entity entity = new() { UpdatedAt = DateTime.Parse("2021-08-21T20:30:15.1234567+08:00") };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
DateTime value = DateTime.Parse("2021-08-21T20:30:15.1234567+08:00");

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,48 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Test.Json;
[ExcludeFromCodeCoverage]
public class DateTimeOffsetConverter_Tests : SerializerTests
{
[Fact]
public void Converter_ReadsJson()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_ReadsJson(string culture)
{
string json = "{\"updatedAt\":\"2021-08-21T12:30:15.123+00:00\"}";
DateTimeOffset value = DateTimeOffset.Parse("2021-08-21T12:30:15.123+00:00");
DateTimeOffset value = new(2021, 8, 21, 12, 30, 15, 123, TimeSpan.Zero);

Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.ToFileTime().Should().Be(value.ToFileTime());
TestWithCulture(culture, () =>
{
Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.ToFileTime().Should().Be(value.ToFileTime());
});
}

[Fact]
public void Converter_WritesJson()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson(string culture)
{
string json = "{\"updatedAt\":\"2021-08-21T12:30:15.123Z\"}";
Entity entity = new() { UpdatedAt = DateTimeOffset.Parse("2021-08-21T12:30:15.1234567+00:00") };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
DateTimeOffset value = new(2021, 8, 21, 12, 30, 15, 123, 456, TimeSpan.Zero);

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
public void Converter_WritesJson_WithTimeZone()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson_WithTimeZone(string culture)
{
string json = "{\"updatedAt\":\"2021-08-21T12:30:15.123Z\"}";
Entity entity = new() { UpdatedAt = DateTimeOffset.Parse("2021-08-21T20:30:15.1234567+08:00") };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
DateTimeOffset value = new(2021, 8, 21, 20, 30, 15, 123, 456, TimeSpan.FromHours(8));

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

using Azure.Core.Serialization;
using CommunityToolkit.Datasync.Server.Abstractions.Json;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

#pragma warning disable IDE0028 // Simplify collection initialization

namespace CommunityToolkit.Datasync.Server.Abstractions.Test.Json;

[ExcludeFromCodeCoverage]
Expand Down Expand Up @@ -37,4 +40,25 @@ public abstract class SerializerTests
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReadCommentHandling = JsonCommentHandling.Skip
};

public static TheoryData<string> Locales => new()
{
"fr-FR",
"da-DA",
"en-US"
};

protected static void TestWithCulture(string culture, Action act)
{
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
try
{
act.Invoke();
}
finally
{
Thread.CurrentThread.CurrentCulture = currentCulture;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,33 @@ namespace CommunityToolkit.Datasync.Server.Abstractions.Test.Json;
[ExcludeFromCodeCoverage]
public class TimeOnlyConverter_Tests : SerializerTests
{
[Fact]
public void Converter_ReadsJson()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_ReadsJson(string culture)
{
string json = """{"updatedAt":"12:30:15.123"}""";
TimeOnly value = TimeOnly.Parse("12:30:15.123");
TimeOnly value = new(12, 30, 15, 123);

Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.Ticks.Should().Be(value.Ticks);
TestWithCulture(culture, () =>
{
Entity entity = JsonSerializer.Deserialize<Entity>(json, SerializerOptions);
entity.UpdatedAt.Ticks.Should().Be(value.Ticks);
});
}

[Fact]
public void Converter_WritesJson()
[Theory]
[MemberData(nameof(Locales))]
public void Converter_WritesJson(string culture)
{
string json = """{"updatedAt":"12:30:15.123"}""";
Entity entity = new() { UpdatedAt = TimeOnly.Parse("12:30:15.1234567") };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
TimeOnly value = new(12, 30, 15, 123, 456);

TestWithCulture(culture, () =>
{
Entity entity = new() { UpdatedAt = value };
string actual = JsonSerializer.Serialize(entity, SerializerOptions);
Assert.Equal(json, actual);
});
}

[Fact]
Expand Down

0 comments on commit ad522d6

Please sign in to comment.