Skip to content

Commit c655f85

Browse files
Fix deserializing null properties to Protobuf types (#52468)
Co-authored-by: James Newton-King <[email protected]>
1 parent 599595c commit c655f85

File tree

3 files changed

+91
-20
lines changed

3 files changed

+91
-20
lines changed

src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonConverterHelper.cs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,51 +78,46 @@ internal static Type GetFieldType(FieldDescriptor descriptor)
7878
}
7979
else
8080
{
81-
return GetFieldTypeCore(descriptor);
82-
}
81+
// Return nullable field types so the serializer can successfully deserialize null value.
82+
return GetFieldTypeCore(descriptor, nullableType: true);
83+
}
8384
}
8485

85-
private static Type GetFieldTypeCore(FieldDescriptor descriptor)
86+
private static Type GetFieldTypeCore(FieldDescriptor descriptor, bool nullableType = false)
8687
{
8788
switch (descriptor.FieldType)
8889
{
8990
case FieldType.Bool:
90-
return typeof(bool);
91+
return nullableType ? typeof(bool?) : typeof(bool);
9192
case FieldType.Bytes:
9293
return typeof(ByteString);
9394
case FieldType.String:
9495
return typeof(string);
9596
case FieldType.Double:
96-
return typeof(double);
97+
return nullableType ? typeof(double?) : typeof(double);
9798
case FieldType.SInt32:
9899
case FieldType.Int32:
99100
case FieldType.SFixed32:
100-
return typeof(int);
101+
return nullableType ? typeof(int?) : typeof(int);
101102
case FieldType.Enum:
102-
return descriptor.EnumType.ClrType;
103+
return nullableType ? typeof(Nullable<>).MakeGenericType(descriptor.EnumType.ClrType) : descriptor.EnumType.ClrType;
103104
case FieldType.Fixed32:
104105
case FieldType.UInt32:
105-
return typeof(uint);
106+
return nullableType ? typeof(uint?) : typeof(uint);
106107
case FieldType.Fixed64:
107108
case FieldType.UInt64:
108-
return typeof(ulong);
109+
return nullableType ? typeof(ulong?) : typeof(ulong);
109110
case FieldType.SFixed64:
110111
case FieldType.Int64:
111112
case FieldType.SInt64:
112-
return typeof(long);
113+
return nullableType ? typeof(long?) : typeof(long);
113114
case FieldType.Float:
114-
return typeof(float);
115+
return nullableType ? typeof(float?) : typeof(float);
115116
case FieldType.Message:
116117
case FieldType.Group: // Never expect to get this, but...
117118
if (ServiceDescriptorHelpers.IsWrapperType(descriptor.MessageType))
118119
{
119-
var t = GetFieldType(descriptor.MessageType.Fields[WrapperValueFieldNumber]);
120-
if (t.IsValueType)
121-
{
122-
return typeof(Nullable<>).MakeGenericType(t);
123-
}
124-
125-
return t;
120+
return GetFieldType(descriptor.MessageType.Fields[WrapperValueFieldNumber]);
126121
}
127122

128123
return descriptor.MessageType.ClrType;

src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/MessageTypeInfoResolver.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,26 @@ private JsonPropertyInfo CreatePropertyInfo(JsonTypeInfo typeInfo, string name,
135135
throw new InvalidOperationException($"Multiple values specified for oneof {field.RealContainingOneof.Name}.");
136136
}
137137

138-
field.Accessor.SetValue((IMessage)o, v);
138+
SetFieldValue(field, (IMessage)o, v);
139139
};
140140
}
141141

142142
return (o, v) =>
143143
{
144-
field.Accessor.SetValue((IMessage)o, v);
144+
SetFieldValue(field, (IMessage)o, v);
145145
};
146+
147+
static void SetFieldValue(FieldDescriptor field, IMessage m, object? v)
148+
{
149+
if (v != null)
150+
{
151+
field.Accessor.SetValue(m, v);
152+
}
153+
else
154+
{
155+
field.Accessor.Clear(m);
156+
}
157+
}
146158
}
147159

148160
private static Dictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)

src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterReadTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,42 @@ public void ReadObjectProperties()
6969
AssertReadJson<HelloRequest>(json);
7070
}
7171

72+
[Fact]
73+
public void ReadNullStringProperty()
74+
{
75+
var json = @"{
76+
""name"": null
77+
}";
78+
79+
AssertReadJson<HelloRequest>(json);
80+
}
81+
82+
[Fact]
83+
public void ReadNullIntProperty()
84+
{
85+
var json = @"{
86+
""age"": null
87+
}";
88+
89+
AssertReadJson<HelloRequest>(json);
90+
}
91+
92+
[Fact]
93+
public void ReadNullProperties()
94+
{
95+
var json = @"{
96+
""age"": null,
97+
""nullValue"": null,
98+
""json_customized_name"": null,
99+
""field_name"": null,
100+
""oneof_name1"": null,
101+
""sub"": null,
102+
""timestamp_value"": null
103+
}";
104+
105+
AssertReadJson<HelloRequest>(json);
106+
}
107+
72108
[Fact]
73109
public void RepeatedStrings()
74110
{
@@ -152,6 +188,34 @@ public void DataTypes_DefaultValues()
152188
AssertReadJson<HelloRequest.Types.DataTypes>(json, descriptorRegistry: serviceDescriptorRegistry);
153189
}
154190

191+
[Fact]
192+
public void DataTypes_NullValues()
193+
{
194+
var json = @"{
195+
""singleInt32"": null,
196+
""singleInt64"": null,
197+
""singleUint32"": null,
198+
""singleUint64"": null,
199+
""singleSint32"": null,
200+
""singleSint64"": null,
201+
""singleFixed32"": null,
202+
""singleFixed64"": null,
203+
""singleSfixed32"": null,
204+
""singleSfixed64"": null,
205+
""singleFloat"": null,
206+
""singleDouble"": null,
207+
""singleBool"": null,
208+
""singleString"": null,
209+
""singleBytes"": null,
210+
""singleEnum"": null
211+
}";
212+
213+
var serviceDescriptorRegistry = new DescriptorRegistry();
214+
serviceDescriptorRegistry.RegisterFileDescriptor(JsonTranscodingGreeter.Descriptor.File);
215+
216+
AssertReadJson<HelloRequest.Types.DataTypes>(json, descriptorRegistry: serviceDescriptorRegistry);
217+
}
218+
155219
[Theory]
156220
[InlineData(1)]
157221
[InlineData(-1)]

0 commit comments

Comments
 (0)