Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative to PR 115 #120

Merged
merged 4 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/NodaTime.Serialization.JsonNet/NodaConverterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
/// to JSON.
/// </summary>
/// <param name="writer">The writer to write JSON data to</param>
/// <param name="value">The value to serializer</param>
/// <param name="value">The value to serialize</param>
/// <param name="serializer">The serializer to use for nested serialization</param>
protected abstract void WriteJsonImpl(JsonWriter writer, T value, JsonSerializer serializer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected override DateTimeZone ReadJsonImpl(JsonReader reader, JsonSerializer s
/// Writes the time zone ID to the writer.
/// </summary>
/// <param name="writer">The writer to write JSON data to</param>
/// <param name="value">The value to serializer</param>
/// <param name="value">The value to serialize</param>
/// <param name="serializer">The serializer to use for nested serialization</param>
protected override void WriteJsonImpl(JsonWriter writer, DateTimeZone value, JsonSerializer serializer)
{
Expand Down
2 changes: 1 addition & 1 deletion src/NodaTime.Serialization.JsonNet/NodaPatternConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected override T ReadJsonImpl(JsonReader reader, JsonSerializer serializer)
/// Writes the formatted value to the writer.
/// </summary>
/// <param name="writer">The writer to write JSON data to</param>
/// <param name="value">The value to serializer</param>
/// <param name="value">The value to serialize</param>
/// <param name="serializer">The serializer to use for nested serialization</param>
protected override void WriteJsonImpl(JsonWriter writer, T value, JsonSerializer serializer)
{
Expand Down
44 changes: 43 additions & 1 deletion src/NodaTime.Serialization.SystemTextJson/NodaConverterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,28 @@ public override T Read(ref Utf8JsonReader reader, Type objectType, JsonSerialize
}
}

/// <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);
}
}

/// <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 +108,33 @@ public override T Read(ref Utf8JsonReader reader, Type objectType, JsonSerialize
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
WriteJsonImpl(writer, value, options);

/// <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) =>
WriteJsonPropertyNameImpl(writer, value, options);

/// <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="value">The value to serialize</param>
/// <param name="options">The serializer options to use for nested serialization</param>
protected abstract void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options);

/// <summary>
/// Implemented by concrete subclasses, this performs the final write operation for a non-null value of type T
/// to JSON, writing the value as a property name. The default implementation throws <see cref="NotImplementedException"/>
/// for compatibility purposes, but all concrete classes within this package override and implement the method fully.
/// </summary>
/// <param name="writer">The writer to write JSON data to</param>
/// <param name="value">The value to serialize</param>
/// <param name="options">The serializer options to use for nested serialization</param>
protected virtual void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,15 @@ protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value,

writer.WriteEndObject();
}

/// <summary>
/// Unconditionally throws an exception, as a DateInterval cannot be serialized as a JSON property name.
/// </summary>
/// <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>
/// <exception cref="InvalidOperationException">Always thrown to indicate this is not an appropriate method to call on this type.</exception>
protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options) =>
throw new JsonException("Cannot serialize a DateInterval as a JSON property name using this converter");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,18 @@ protected override DateTimeZone ReadJsonImpl(ref Utf8JsonReader reader, JsonSeri
/// Writes the time zone ID to the writer.
/// </summary>
/// <param name="writer">The writer to write JSON data to.</param>
/// <param name="value">The value to serializer.</param>
/// <param name="value">The value to serialize.</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);

/// <summary>
/// Writes the time zone ID to the writer as a property name
/// </summary>
/// <param name="writer">The writer to write JSON data to.</param>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The serialization options to use for nested serialization.</param>
protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, DateTimeZone value, JsonSerializerOptions options) =>
writer.WritePropertyName(value.Id);
}
}
10 changes: 10 additions & 0 deletions src/NodaTime.Serialization.SystemTextJson/NodaIntervalConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,15 @@ protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, Jso
}
writer.WriteEndObject();
}

/// <summary>
/// Unconditionally throws an exception, as an Interval cannot be serialized as a JSON property name.
/// </summary>
/// <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>
/// <exception cref="InvalidOperationException">Always thrown to indicate this is not an appropriate method to call on this type.</exception>
protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options) =>
throw new JsonException("Cannot serialize an Interval as a JSON property name using this converter");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,15 @@ protected override DateInterval ReadJsonImpl(ref Utf8JsonReader reader, JsonSeri
protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options)
{
var pattern = LocalDatePattern.Iso;
string text = pattern.Format(value.Start) + "/" + pattern.Format(value.End);
var text = $"{pattern.Format(value.Start)}/{pattern.Format(value.End)}";
writer.WriteStringValue(text);
}

protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options)
{
var pattern = LocalDatePattern.Iso;
var text = $"{pattern.Format(value.Start)}/{pattern.Format(value.End)}";
writer.WritePropertyName(text);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,15 @@ protected override Interval ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializ
protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options)
{
var pattern = InstantPattern.ExtendedIso;
string text = (value.HasStart ? pattern.Format(value.Start) : "") + "/" + (value.HasEnd ? pattern.Format(value.End) : "");
var text = $"{(value.HasStart ? pattern.Format(value.Start) : "")}/{(value.HasEnd ? pattern.Format(value.End) : "")}";
writer.WriteStringValue(text);
}

protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options)
{
var pattern = InstantPattern.ExtendedIso;
var text = $"{(value.HasStart ? pattern.Format(value.Start) : "")}/{(value.HasEnd ? pattern.Format(value.End) : "")}";
writer.WritePropertyName(text);
}
}
}
20 changes: 17 additions & 3 deletions src/NodaTime.Serialization.SystemTextJson/NodaPatternConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Use of this source code is governed by the Apache License 2.0,
// as found in the LICENSE.txt file.

using NodaTime.Text;
using System;
using System.Text.Json;
using NodaTime.Text;

namespace NodaTime.Serialization.SystemTextJson
{
Expand Down Expand Up @@ -59,12 +59,26 @@ protected override T ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptio
/// Writes the formatted value to the writer.
/// </summary>
/// <param name="writer">The writer to write JSON data to</param>
/// <param name="value">The value to serializer</param>
/// <param name="value">The value to serialize</param>
/// <param name="options">The serializer options to use for nested serialization</param>
protected override void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
validator?.Invoke(value);
writer.WriteStringValue(pattern.Format(value));
var text = pattern.Format(value);
writer.WriteStringValue(text);
}

/// <summary>
/// Writes the formatted value to the writer.
/// </summary>
/// <param name="writer">The writer to write JSON data to.</param>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The serialization options to use for nested serialization.</param>
protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
validator?.Invoke(value);
var text = pattern.Format(value);
writer.WritePropertyName(text);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ 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) =>
writer.WriteStringValue(value.ToString());
}

protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, int value, JsonSerializerOptions options) =>
writer.WritePropertyName(value.ToString());
}

private class TestStringConverter : NodaConverterBase<string>
Expand All @@ -88,10 +89,11 @@ 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) =>
writer.WriteStringValue(value);
}

protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
writer.WritePropertyName(value);
}
}
}
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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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

Expand Down Expand Up @@ -135,7 +136,7 @@ public void Deserialize_CaseInsensitive()
string json = "{\"Interval\":{\"Start\":\"2012-01-02\",\"End\":\"2013-06-07\"}}";

var testObjectPascalCase = JsonSerializer.Deserialize<TestObject>(json, optionsCaseInsensitive);;
var testObjectCamelCase = JsonSerializer.Deserialize<TestObject>(json, optionsCamelCaseCaseInsensitive); ;
var testObjectCamelCase = JsonSerializer.Deserialize<TestObject>(json, optionsCamelCaseCaseInsensitive);

var intervalPascalCase = testObjectPascalCase.Interval;
var intervalCamelCase = testObjectCamelCase.Interval;
Expand Down Expand Up @@ -165,6 +166,16 @@ public void Deserialize_CaseInsensitive_CamelCase()
Assert.AreEqual(expectedInterval, intervalCamelCase);
}

[Test]
public void CannotUseDateIntervalAsPropertyName()
{
var obj = new Dictionary<DateInterval, string>
{
{ new DateInterval(new LocalDate(2012, 1, 2), new LocalDate(2013, 6, 7)), "Test" }
};
Assert.Throws<JsonException>(() => JsonSerializer.Serialize(obj, options));
}

public class TestObject
{
public DateInterval Interval { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by the Apache License 2.0,
// as found in the LICENSE.txt file.

using System.Collections.Generic;
using System.Text.Json;
using NodaTime.Serialization.SystemTextJson;
using NUnit.Framework;
Expand Down Expand Up @@ -118,7 +119,7 @@ public void Deserialize_CaseSensitive()
{
string json = "{\"Interval\":{\"Start\":\"2012-01-02T03:04:05Z\",\"end\":\"2013-06-07T08:09:10Z\"}}";

var testObject = JsonSerializer.Deserialize<TestObject>(json, options); ;
var testObject = JsonSerializer.Deserialize<TestObject>(json, options);

Assert.True(testObject.Interval.HasStart);
Assert.False(testObject.Interval.HasEnd);
Expand All @@ -129,7 +130,7 @@ public void Deserialize_CaseSensitive_CamelCase()
{
string json = "{\"interval\":{\"Start\":\"2012-01-02T03:04:05Z\",\"end\":\"2013-06-07T08:09:10Z\"}}";

var testObject = JsonSerializer.Deserialize<TestObject>(json, optionsCamelCase); ;
var testObject = JsonSerializer.Deserialize<TestObject>(json, optionsCamelCase);

Assert.False(testObject.Interval.HasStart);
Assert.True(testObject.Interval.HasEnd);
Expand All @@ -140,8 +141,8 @@ public void Deserialize_CaseInsensitive()
{
string json = "{\"Interval\":{\"Start\":\"2012-01-02T03:04:05Z\",\"End\":\"2013-06-07T08:09:10Z\"}}";

var testObjectPascalCase = JsonSerializer.Deserialize<TestObject>(json, optionsCaseInsensitive); ;
var testObjectCamelCase = JsonSerializer.Deserialize<TestObject>(json, optionsCamelCaseCaseInsensitive); ;
var testObjectPascalCase = JsonSerializer.Deserialize<TestObject>(json, optionsCaseInsensitive);
var testObjectCamelCase = JsonSerializer.Deserialize<TestObject>(json, optionsCamelCaseCaseInsensitive);

var intervalPascalCase = testObjectPascalCase.Interval;
var intervalCamelCase = testObjectCamelCase.Interval;
Expand Down Expand Up @@ -171,6 +172,16 @@ public void Deserialize_CaseInsensitive_CamelCase()
Assert.AreEqual(expectedInterval, intervalCamelCase);
}

[Test]
public void CannotUseIntervalAsPropertyName()
{
var obj = new Dictionary<Interval, string>
{
{ new Interval(NodaConstants.UnixEpoch, NodaConstants.UnixEpoch), "Test" }
};
Assert.Throws<JsonException>(() => JsonSerializer.Serialize(obj, options));
}

public class TestObject
{
public Interval Interval { get; set; }
Expand Down
Loading