Skip to content

Commit 2ec9740

Browse files
authored
Don't error on Protobuf messages that expose wrapper types (#62871)
1 parent 8eeda31 commit 2ec9740

File tree

4 files changed

+808
-7
lines changed

4 files changed

+808
-7
lines changed

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

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections;
54
using System.Diagnostics;
65
using System.Diagnostics.CodeAnalysis;
76
using System.Text.Json;
@@ -94,6 +93,23 @@ private JsonPropertyInfo CreatePropertyInfo(JsonTypeInfo typeInfo, string name,
9493
JsonConverterHelper.GetFieldType(field),
9594
name);
9695

96+
// A property with a wrapper type is usually the underlying type on the DTO.
97+
// For example, a field of type google.protobuf.StringValue will have a property of type string.
98+
// However, the wrapper type is exposed if someone manually creates a DTO with it, or there is a problem
99+
// detecting wrapper type in code generation. For example, https://github.com/protocolbuffers/protobuf/issues/22744
100+
FieldDescriptor? wrapperTypeValueField = null;
101+
if (field.FieldType == FieldType.Message && ServiceDescriptorHelpers.IsWrapperType(field.MessageType))
102+
{
103+
var property = field.ContainingType.ClrType.GetProperty(field.PropertyName);
104+
105+
// Check if the property type is the same as the field type. This means the property is StringValue, et al,
106+
// and additional conversion is required.
107+
if (property != null && property.PropertyType == field.MessageType.ClrType)
108+
{
109+
wrapperTypeValueField = field.MessageType.FindFieldByName("value");
110+
}
111+
}
112+
97113
propertyInfo.ShouldSerialize = (o, v) =>
98114
{
99115
// Properties that don't have this flag set are only used to deserialize incoming JSON.
@@ -105,7 +121,13 @@ private JsonPropertyInfo CreatePropertyInfo(JsonTypeInfo typeInfo, string name,
105121
};
106122
propertyInfo.Get = (o) =>
107123
{
108-
return field.Accessor.GetValue((IMessage)o);
124+
var value = field.Accessor.GetValue((IMessage)o);
125+
if (wrapperTypeValueField != null && value is IMessage wrapperMessage)
126+
{
127+
return wrapperTypeValueField.Accessor.GetValue(wrapperMessage);
128+
}
129+
130+
return value;
109131
};
110132

111133
if (field.IsMap || field.IsRepeated)
@@ -115,13 +137,13 @@ private JsonPropertyInfo CreatePropertyInfo(JsonTypeInfo typeInfo, string name,
115137
}
116138
else
117139
{
118-
propertyInfo.Set = GetSetMethod(field);
140+
propertyInfo.Set = GetSetMethod(field, wrapperTypeValueField);
119141
}
120142

121143
return propertyInfo;
122144
}
123145

124-
private static Action<object, object?> GetSetMethod(FieldDescriptor field)
146+
private static Action<object, object?> GetSetMethod(FieldDescriptor field, FieldDescriptor? wrapperTypeValueField)
125147
{
126148
Debug.Assert(!field.IsRepeated && !field.IsMap, "Collections shouldn't have a setter.");
127149

@@ -135,19 +157,27 @@ private JsonPropertyInfo CreatePropertyInfo(JsonTypeInfo typeInfo, string name,
135157
throw new InvalidOperationException($"Multiple values specified for oneof {field.RealContainingOneof.Name}.");
136158
}
137159

138-
SetFieldValue(field, (IMessage)o, v);
160+
SetFieldValue(field, wrapperTypeValueField, (IMessage)o, v);
139161
};
140162
}
141163

142164
return (o, v) =>
143165
{
144-
SetFieldValue(field, (IMessage)o, v);
166+
SetFieldValue(field, wrapperTypeValueField, (IMessage)o, v);
145167
};
146168

147-
static void SetFieldValue(FieldDescriptor field, IMessage m, object? v)
169+
static void SetFieldValue(FieldDescriptor field, FieldDescriptor? wrapperTypeValueField, IMessage m, object? v)
148170
{
149171
if (v != null)
150172
{
173+
// This field exposes a wrapper type. Need to create a wrapper instance and set the value on it.
174+
if (wrapperTypeValueField != null && v is not IMessage)
175+
{
176+
var wrapper = (IMessage)Activator.CreateInstance(field.MessageType.ClrType)!;
177+
wrapperTypeValueField.Accessor.SetValue(wrapper, v);
178+
v = wrapper;
179+
}
180+
151181
field.Accessor.SetValue(m, v);
152182
}
153183
else

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
1212
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
1313
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure;
14+
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.TestObjects.ProtobufMessages;
1415
using Transcoding;
1516
using Xunit.Abstractions;
1617

@@ -531,6 +532,25 @@ public void NullableWrappers()
531532
AssertReadJson<HelloRequest.Types.Wrappers>(json);
532533
}
533534

535+
[Fact]
536+
public void NullableWrappers_Type()
537+
{
538+
var json = @"{
539+
""stringValue"": ""A string"",
540+
""int32Value"": 1,
541+
""int64Value"": ""2"",
542+
""floatValue"": 1.2,
543+
""doubleValue"": 1.1,
544+
""boolValue"": true,
545+
""uint32Value"": 3,
546+
""uint64Value"": ""4"",
547+
""bytesValue"": ""SGVsbG8gd29ybGQ=""
548+
}";
549+
550+
var result = AssertReadJson<WrappersMessage>(json, serializeOld: false);
551+
Assert.Equal("A string", result.StringValue.Value);
552+
}
553+
534554
[Fact]
535555
public void NullValue_Default_Null()
536556
{

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
1212
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
1313
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure;
14+
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.TestObjects.ProtobufMessages;
1415
using Transcoding;
1516
using Xunit.Abstractions;
1617
using Type = System.Type;
@@ -201,6 +202,25 @@ public void NullableWrappers()
201202
AssertWrittenJson(wrappers);
202203
}
203204

205+
[Fact]
206+
public void NullableWrappers_Types()
207+
{
208+
var wrappers = new WrappersMessage
209+
{
210+
BoolValue = new BoolValue { Value = true },
211+
BytesValue = new BytesValue { Value = ByteString.CopyFrom(Encoding.UTF8.GetBytes("Hello world")) },
212+
DoubleValue = new DoubleValue { Value = 1.1 },
213+
FloatValue = new FloatValue { Value = 1.2f },
214+
Int32Value = new Int32Value { Value = 1 },
215+
Int64Value = new Int64Value { Value = 2L },
216+
StringValue = new StringValue { Value = "A string" },
217+
Uint32Value = new UInt32Value { Value = 3U },
218+
Uint64Value = new UInt64Value { Value = 4UL }
219+
};
220+
221+
AssertWrittenJson(wrappers);
222+
}
223+
204224
[Fact]
205225
public void NullableWrapper_Root_Int32()
206226
{

0 commit comments

Comments
 (0)