Skip to content

Commit 0d19045

Browse files
committed
Add Duration support
1 parent 980e203 commit 0d19045

10 files changed

Lines changed: 197 additions & 3 deletions

src/Protobuf.System.Text.Json/JsonProtobufSerializerOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ public class JsonProtobufSerializerOptions
77
/// When set to true PropertyNamingPolicy will be ignored and property name will be derived from protobuf contract.
88
/// This is usually the lower-camel-cased form of the field name, but can be overridden using the <c>json_name</c>
99
/// option in the .proto file.
10+
/// The default value is false.
1011
/// </summary>
1112
public bool UseProtobufJsonNames { get; set; }
13+
14+
/// <summary>
15+
/// Controls how <see cref="Google.Protobuf.WellKnownTypes.Duration"/> fields are handled.
16+
/// When set to true, <see cref="Google.Protobuf.WellKnownTypes.Duration"/> properties will
17+
/// be converted to <see cref="TimeSpan"/> before serialization and will be expected in the
18+
/// same format as <see cref="TimeSpan"/> (-dddddddd.hh:mm:ss.fffffff) during deserialization.
19+
/// The default value is true.
20+
/// </summary>
21+
public bool TreatDurationAsTimeSpan { get; set; } = true;
1222
}

src/Protobuf.System.Text.Json/JsonSerializerOptionsExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Protobuf.System.Text.Json;
2+
using Protobuf.System.Text.Json.WellKnownTypesConverters;
23

34
// ReSharper disable once CheckNamespace
45
namespace System.Text.Json;
@@ -14,6 +15,11 @@ public static void AddProtobufSupport(this JsonSerializerOptions options, Action
1415
{
1516
var jsonProtobufSerializerOptions = new JsonProtobufSerializerOptions();
1617
configure.Invoke(jsonProtobufSerializerOptions);
18+
if (jsonProtobufSerializerOptions.TreatDurationAsTimeSpan)
19+
{
20+
options.Converters.Add(new DurationConverter());
21+
}
22+
1723
options.Converters.Add(new TimestampConverter());
1824
options.Converters.Add(new ProtobufJsonConverterFactory(jsonProtobufSerializerOptions));
1925
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
using Google.Protobuf.WellKnownTypes;
4+
using Type = System.Type;
5+
6+
namespace Protobuf.System.Text.Json.WellKnownTypesConverters;
7+
8+
internal class DurationConverter : JsonConverter<Duration?>
9+
{
10+
private JsonConverter<TimeSpan>? _converter;
11+
12+
public override Duration? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
13+
{
14+
_converter ??= (JsonConverter<TimeSpan>) options.GetConverter(typeof(TimeSpan));
15+
var timeSpan = _converter.Read(ref reader, typeToConvert, options);
16+
return timeSpan.ToDuration();
17+
}
18+
19+
public override void Write(Utf8JsonWriter writer, Duration? value, JsonSerializerOptions options)
20+
{
21+
if (value != null)
22+
{
23+
var timeSpan = value.ToTimeSpan();
24+
_converter ??= (JsonConverter<TimeSpan>) options.GetConverter(typeof(TimeSpan));
25+
_converter.Write(writer, timeSpan, options);
26+
}
27+
}
28+
}

src/Protobuf.System.Text.Json/TimestampConverter.cs renamed to src/Protobuf.System.Text.Json/WellKnownTypesConverters/TimestampConverter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
using Google.Protobuf.WellKnownTypes;
44
using Type = System.Type;
55

6-
namespace Protobuf.System.Text.Json;
6+
namespace Protobuf.System.Text.Json.WellKnownTypesConverters;
77

8-
public class TimestampConverter : JsonConverter<Timestamp?>
8+
internal class TimestampConverter : JsonConverter<Timestamp?>
99
{
1010
private JsonConverter<DateTime>? _converter;
1111

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"durationProperty": {
3+
"seconds": 256,
4+
"nanos": 512
5+
}
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"durationProperty": "00:04:16.0000005"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Protobuf.Tests;
3+
using Google.Protobuf.WellKnownTypes;
4+
using Protobuf.System.Text.Json.Tests.Utils;
5+
using Shouldly;
6+
using SmartAnalyzers.ApprovalTestsExtensions;
7+
using Xunit;
8+
9+
namespace Protobuf.System.Text.Json.Tests;
10+
11+
public class MessageWithDurationTests
12+
{
13+
[Fact]
14+
public void Should_serialize_message_with_duration()
15+
{
16+
// Arrange
17+
var msg = new MessageWithDuration
18+
{
19+
DurationProperty = new Duration {Seconds = 256, Nanos = 512}
20+
};
21+
22+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
23+
24+
// Act
25+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
26+
27+
// Assert
28+
var approver = new ExplicitApprover();
29+
approver.VerifyJson(serialized);
30+
}
31+
32+
[Fact]
33+
public void Should_serialize_message_with_duration_when_value_not_set()
34+
{
35+
// Arrange
36+
var msg = new MessageWithDuration();
37+
38+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
39+
40+
// Act
41+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
42+
43+
// Assert
44+
var approver = new ExplicitApprover();
45+
approver.VerifyJson(serialized);
46+
}
47+
48+
[Fact]
49+
public void Should_serialize_and_deserialize_message_with_duration()
50+
{
51+
// Arrange
52+
var msg = new MessageWithDuration
53+
{
54+
DurationProperty = new Duration {Seconds = 256, Nanos = 500}
55+
};
56+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
57+
58+
// Act
59+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
60+
var deserialized = JsonSerializer.Deserialize<MessageWithDuration>(serialized, jsonSerializerOptions);
61+
62+
// Assert
63+
deserialized.ShouldNotBeNull();
64+
deserialized.ShouldBeEquivalentTo(msg);
65+
}
66+
67+
[Fact]
68+
public void Should_serialize_and_deserialize_message_with_duration_when_value_is_not_set()
69+
{
70+
// Arrange
71+
var msg = new MessageWithDuration();
72+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
73+
74+
// Act
75+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
76+
var deserialized = JsonSerializer.Deserialize<MessageWithDuration>(serialized, jsonSerializerOptions);
77+
78+
// Assert
79+
deserialized.ShouldNotBeNull();
80+
deserialized.ShouldBeEquivalentTo(msg);
81+
}
82+
83+
[Fact]
84+
public void Should_serialize_duration_property_as_complex_object_when_TreatDurationAsTimeSpan_option_set_to_false()
85+
{
86+
// Arrange
87+
var msg = new MessageWithDuration
88+
{
89+
DurationProperty = new Duration {Seconds = 256, Nanos = 512}
90+
};
91+
92+
var jsonSerializerOptions = new JsonSerializerOptions();
93+
jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
94+
jsonSerializerOptions.AddProtobufSupport(options =>
95+
{
96+
options.TreatDurationAsTimeSpan = false;
97+
});
98+
99+
// Act
100+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
101+
102+
// Assert
103+
var approver = new ExplicitApprover();
104+
approver.VerifyJson(serialized);
105+
}
106+
107+
[Fact]
108+
public void Should_deserialize_duration_property_from_complex_object_when_TreatDurationAsTimeSpan_option_set_to_false()
109+
{
110+
// Arrange
111+
var msg = new MessageWithDuration
112+
{
113+
DurationProperty = new Duration {Seconds = 256, Nanos = 512}
114+
};
115+
116+
var jsonSerializerOptions = new JsonSerializerOptions();
117+
jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
118+
jsonSerializerOptions.AddProtobufSupport(options =>
119+
{
120+
options.TreatDurationAsTimeSpan = false;
121+
});
122+
123+
// Act
124+
var payload = JsonSerializer.Serialize(msg, jsonSerializerOptions);
125+
var deserialized = JsonSerializer.Deserialize<MessageWithDuration>(payload, jsonSerializerOptions);
126+
127+
// Assert
128+
deserialized.ShouldNotBeNull();
129+
deserialized.ShouldBeEquivalentTo(msg);
130+
131+
}
132+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
syntax = "proto3";
2+
3+
option csharp_namespace = "System.Text.Json.Protobuf.Tests";
4+
5+
import "google/protobuf/duration.proto";
6+
7+
message MessageWithDuration {
8+
google.protobuf.Duration duration_property = 1;
9+
}

test/Protobuf.System.Text.Json.Tests/Protos/message_with_timestamp.proto

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ syntax = "proto3";
33
option csharp_namespace = "System.Text.Json.Protobuf.Tests";
44

55
import "google/protobuf/timestamp.proto";
6-
import "google/protobuf/duration.proto";
76

87
message MessageWithTimestamp {
98
google.protobuf.Timestamp timestamp_property = 1;

0 commit comments

Comments
 (0)