Skip to content

Commit 19a1c35

Browse files
committed
Add oneof support
1 parent fbe8c43 commit 19a1c35

10 files changed

Lines changed: 200 additions & 17 deletions

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ internal class FieldInfo
99
public bool IsRepeated { get; init; }
1010
public Type FieldType { get; init; }
1111
public string JsonName { get; set; }
12+
public bool IsOneOf { get; set; }
1213
}

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

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,35 @@ namespace System.Text.Json.Protobuf;
77

88
internal class ProtobufConverter<T> : JsonConverter<T?> where T : class, IMessage, new()
99
{
10-
private readonly List<FieldInfo> _fields = new();
11-
private readonly Dictionary<string, FieldInfo> _fieldsLookup = new();
12-
10+
private readonly FieldInfo[] _fields;
11+
private readonly Dictionary<string, FieldInfo> _fieldsLookup;
12+
1313
public ProtobufConverter()
1414
{
1515
var propertyInfo = typeof(T).GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static);
1616
var messageDescriptor = (MessageDescriptor) propertyInfo?.GetValue(null, null)!;
1717

18-
foreach (var fieldDescriptor in messageDescriptor.Fields.InDeclarationOrder())
18+
_fields = messageDescriptor.Fields.InDeclarationOrder().Select(fieldDescriptor => new FieldInfo
1919
{
20-
var fieldInfo = new FieldInfo
21-
{
22-
Accessor = fieldDescriptor.Accessor,
23-
IsRepeated = fieldDescriptor.IsRepeated,
24-
FieldType = GetFieldType(fieldDescriptor),
25-
JsonName = fieldDescriptor.JsonName,
26-
};
27-
_fieldsLookup.Add(fieldDescriptor.JsonName, fieldInfo);
28-
_fields.Add(fieldInfo);
29-
}
20+
Accessor = fieldDescriptor.Accessor,
21+
IsRepeated = fieldDescriptor.IsRepeated,
22+
FieldType = GetFieldType(fieldDescriptor),
23+
JsonName = fieldDescriptor.JsonName,
24+
IsOneOf = fieldDescriptor.ContainingOneof != null
25+
}).ToArray();
26+
27+
_fieldsLookup = _fields.ToDictionary(x => x.JsonName, x => x);
3028
}
3129

32-
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
30+
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
3331
{
3432
var obj = new T();
3533

3634
if (reader.TokenType != JsonTokenType.StartObject)
3735
{
3836
throw new JsonException($"The JSON value could not be converted to {typeToConvert}.");
3937
}
40-
38+
4139
// Process all properties.
4240
while (true)
4341
{
@@ -63,7 +61,6 @@ public ProtobufConverter()
6361
}
6462

6563
return obj;
66-
6764
}
6865

6966
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
@@ -78,6 +75,11 @@ public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOption
7875

7976
foreach (var fieldInfo in _fields)
8077
{
78+
if (fieldInfo.IsOneOf && fieldInfo.Accessor.HasValue(value) == false)
79+
{
80+
continue;
81+
}
82+
8183
var obj = fieldInfo.Accessor.GetValue(value);
8284
if (obj is { } propertyValue)
8385
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"int32PropertyValue": 5
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"int32Property": 5
3+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
using System.Text.Json.Protobuf.Tests.Utils;
2+
using Shouldly;
3+
using SmartAnalyzers.ApprovalTestsExtensions;
4+
using Xunit;
5+
6+
namespace System.Text.Json.Protobuf.Tests;
7+
8+
public class MessageWithOneOfTests
9+
{
10+
[Fact]
11+
public void Should_serialize_message_with_one_of_when_value_is_set()
12+
{
13+
// Arrange
14+
var msg = new MessageWithOneOf
15+
{
16+
Int32PropertyValue = 5
17+
};
18+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
19+
20+
// Act
21+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
22+
23+
// Assert
24+
var approver = new ExplicitApprover();
25+
approver.VerifyJson(serialized);
26+
}
27+
28+
[Fact]
29+
public void Should_serialize_message_with_one_of_when_value_is_not_set()
30+
{
31+
// Arrange
32+
var msg = new MessageWithOneOf();
33+
34+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
35+
36+
// Act
37+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
38+
39+
// Assert
40+
var approver = new ExplicitApprover();
41+
approver.VerifyJson(serialized);
42+
}
43+
44+
[Fact]
45+
public void Should_serialize_and_deserialize_message_with_one_of_when_value_is_set()
46+
{
47+
// Arrange
48+
var msg = new MessageWithOneOf
49+
{
50+
Int64PropertyValue = 5
51+
};
52+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
53+
54+
// Act
55+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
56+
var deserialized = JsonSerializer.Deserialize<MessageWithOneOf>(serialized, jsonSerializerOptions);
57+
58+
// Assert
59+
deserialized.ShouldNotBeNull();
60+
deserialized.Int32PropertyValue.ShouldBe(msg.Int32PropertyValue);
61+
deserialized.Int64PropertyValue.ShouldBe(msg.Int64PropertyValue);
62+
deserialized.PropertyCase.ShouldBe(msg.PropertyCase);
63+
}
64+
65+
[Fact]
66+
public void Should_serialize_and_deserialize_message_with_one_of_when_value_is_not_set()
67+
{
68+
// Arrange
69+
var msg = new MessageWithOneOf();
70+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
71+
72+
// Act
73+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
74+
var deserialized = JsonSerializer.Deserialize<MessageWithOneOf>(serialized, jsonSerializerOptions);
75+
76+
// Assert
77+
deserialized.ShouldNotBeNull();
78+
deserialized.Int32PropertyValue.ShouldBe(msg.Int32PropertyValue);
79+
deserialized.Int64PropertyValue.ShouldBe(msg.Int64PropertyValue);
80+
deserialized.PropertyCase.ShouldBe(msg.PropertyCase);
81+
}
82+
83+
[Fact]
84+
public void Should_serialize_message_with_optional_property_when_value_is_set()
85+
{
86+
// Arrange
87+
var msg = new MessageWithOptionalProperty
88+
{
89+
Int32Property = 5
90+
};
91+
92+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
93+
94+
// Act
95+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
96+
97+
// Assert
98+
var approver = new ExplicitApprover();
99+
approver.VerifyJson(serialized);
100+
}
101+
102+
[Fact]
103+
public void Should_serialize_message_with_optional_property_when_value_is_not_set()
104+
{
105+
// Arrange
106+
var msg = new MessageWithOptionalProperty();
107+
108+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
109+
110+
// Act
111+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
112+
113+
// Assert
114+
var approver = new ExplicitApprover();
115+
approver.VerifyJson(serialized);
116+
}
117+
118+
[Fact]
119+
public void Should_serialize_and_deserialize_message_with_optional_property_when_value_is_set()
120+
{
121+
// Arrange
122+
var msg = new MessageWithOptionalProperty()
123+
{
124+
Int32Property = 5
125+
};
126+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
127+
128+
// Act
129+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
130+
var deserialized = JsonSerializer.Deserialize<MessageWithOptionalProperty>(serialized, jsonSerializerOptions);
131+
132+
// Assert
133+
deserialized.ShouldNotBeNull();
134+
deserialized.Int32Property.ShouldBe(msg.Int32Property);
135+
deserialized.HasInt32Property.ShouldBe(msg.HasInt32Property);
136+
}
137+
138+
[Fact]
139+
public void Should_serialize_and_deserialize_message_with_optional_property_when_value_is_not_set()
140+
{
141+
// Arrange
142+
var msg = new MessageWithOptionalProperty();
143+
var jsonSerializerOptions = TestHelper.CreateJsonSerializerOptions();
144+
145+
// Act
146+
var serialized = JsonSerializer.Serialize(msg, jsonSerializerOptions);
147+
var deserialized = JsonSerializer.Deserialize<MessageWithOptionalProperty>(serialized, jsonSerializerOptions);
148+
149+
// Assert
150+
deserialized.ShouldNotBeNull();
151+
deserialized.Int32Property.ShouldBe(msg.Int32Property);
152+
deserialized.HasInt32Property.ShouldBe(msg.HasInt32Property);
153+
}
154+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
syntax = "proto3";
2+
3+
option csharp_namespace = "System.Text.Json.Protobuf.Tests";
4+
5+
message MessageWithOneOf {
6+
oneof property {
7+
int32 int_32_property_value = 1;
8+
int64 int_64_property_value = 2;
9+
}
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto3";
2+
3+
option csharp_namespace = "System.Text.Json.Protobuf.Tests";
4+
5+
message MessageWithOptionalProperty {
6+
optional int32 int_32_property = 1;
7+
}

0 commit comments

Comments
 (0)