Skip to content
代码片段 群组 项目
未验证 提交 7650c58e 编辑于 作者: James Newton-King's avatar James Newton-King 提交者: GitHub
浏览文件

Add message type resolver (#42428)

上级 92834c8a
No related branches found
No related tags found
无相关合并请求
显示
341 个添加243 个删除
...@@ -86,9 +86,27 @@ internal sealed class AnyConverter<TMessage> : SettingsConverterBase<TMessage> w ...@@ -86,9 +86,27 @@ internal sealed class AnyConverter<TMessage> : SettingsConverterBase<TMessage> w
} }
else else
{ {
MessageConverter<Any>.WriteMessageFields(writer, valueMessage, Context.Settings, options); WriteMessageFields(writer, valueMessage, Context.Settings, options);
} }
writer.WriteEndObject(); writer.WriteEndObject();
} }
internal static void WriteMessageFields(Utf8JsonWriter writer, IMessage message, GrpcJsonSettings settings, JsonSerializerOptions options)
{
var fields = message.Descriptor.Fields;
foreach (var field in fields.InFieldNumberOrder())
{
var accessor = field.Accessor;
var value = accessor.GetValue(message);
if (!JsonConverterHelper.ShouldFormatFieldValue(message, field, value, !settings.IgnoreDefaultValues))
{
continue;
}
writer.WritePropertyName(accessor.Descriptor.JsonName);
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
} }
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class JsonConverterFactoryForMessage : JsonConverterFactory
{
private readonly JsonContext _context;
public JsonConverterFactoryForMessage(JsonContext context)
{
_context = context;
}
public override bool CanConvert(Type typeToConvert)
{
return typeof(IMessage).IsAssignableFrom(typeToConvert);
}
public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options)
{
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
typeof(MessageConverter<>).MakeGenericType(new Type[] { typeToConvert }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { _context },
culture: null)!;
return converter;
}
}
...@@ -5,7 +5,6 @@ using System.Reflection; ...@@ -5,7 +5,6 @@ using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Google.Protobuf; using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type; using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json; namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
...@@ -32,14 +31,14 @@ internal sealed class JsonConverterFactoryForWellKnownTypes : JsonConverterFacto ...@@ -32,14 +31,14 @@ internal sealed class JsonConverterFactoryForWellKnownTypes : JsonConverterFacto
return false; return false;
} }
return WellKnownTypeNames.ContainsKey(descriptor.FullName); return JsonConverterHelper.WellKnownTypeNames.ContainsKey(descriptor.FullName);
} }
public override JsonConverter CreateConverter( public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options) Type typeToConvert, JsonSerializerOptions options)
{ {
var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert)!; var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert)!;
var converterType = WellKnownTypeNames[descriptor.FullName]; var converterType = JsonConverterHelper.WellKnownTypeNames[descriptor.FullName];
var converter = (JsonConverter)Activator.CreateInstance( var converter = (JsonConverter)Activator.CreateInstance(
converterType.MakeGenericType(new Type[] { typeToConvert }), converterType.MakeGenericType(new Type[] { typeToConvert }),
...@@ -50,15 +49,4 @@ internal sealed class JsonConverterFactoryForWellKnownTypes : JsonConverterFacto ...@@ -50,15 +49,4 @@ internal sealed class JsonConverterFactoryForWellKnownTypes : JsonConverterFacto
return converter; return converter;
} }
private static readonly Dictionary<string, Type> WellKnownTypeNames = new Dictionary<string, Type>
{
[Any.Descriptor.FullName] = typeof(AnyConverter<>),
[Duration.Descriptor.FullName] = typeof(DurationConverter<>),
[Timestamp.Descriptor.FullName] = typeof(TimestampConverter<>),
[FieldMask.Descriptor.FullName] = typeof(FieldMaskConverter<>),
[Struct.Descriptor.FullName] = typeof(StructConverter<>),
[ListValue.Descriptor.FullName] = typeof(ListValueConverter<>),
[Value.Descriptor.FullName] = typeof(ValueConverter<>),
};
} }
...@@ -6,7 +6,9 @@ using System.Reflection; ...@@ -6,7 +6,9 @@ using System.Reflection;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Google.Protobuf; using Google.Protobuf;
using Google.Protobuf.Collections;
using Google.Protobuf.Reflection; using Google.Protobuf.Reflection;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using Grpc.Shared; using Grpc.Shared;
...@@ -18,17 +20,33 @@ internal static class JsonConverterHelper ...@@ -18,17 +20,33 @@ internal static class JsonConverterHelper
{ {
internal const int WrapperValueFieldNumber = Int32Value.ValueFieldNumber; internal const int WrapperValueFieldNumber = Int32Value.ValueFieldNumber;
internal static readonly Dictionary<string, Type> WellKnownTypeNames = new Dictionary<string, Type>
{
[Any.Descriptor.FullName] = typeof(AnyConverter<>),
[Duration.Descriptor.FullName] = typeof(DurationConverter<>),
[Timestamp.Descriptor.FullName] = typeof(TimestampConverter<>),
[FieldMask.Descriptor.FullName] = typeof(FieldMaskConverter<>),
[Struct.Descriptor.FullName] = typeof(StructConverter<>),
[ListValue.Descriptor.FullName] = typeof(ListValueConverter<>),
[Value.Descriptor.FullName] = typeof(ValueConverter<>),
};
internal static JsonSerializerOptions CreateSerializerOptions(JsonContext context, bool isStreamingOptions = false) internal static JsonSerializerOptions CreateSerializerOptions(JsonContext context, bool isStreamingOptions = false)
{ {
// Streaming is line delimited between messages. That means JSON can't be indented as it adds new lines. // Streaming is line delimited between messages. That means JSON can't be indented as it adds new lines.
// For streaming to work, indenting must be disabled when streaming. // For streaming to work, indenting must be disabled when streaming.
var writeIndented = !isStreamingOptions ? context.Settings.WriteIndented : false; var writeIndented = !isStreamingOptions ? context.Settings.WriteIndented : false;
var typeInfoResolver = JsonTypeInfoResolver.Combine(
new MessageTypeInfoResolver(context),
new DefaultJsonTypeInfoResolver());
var options = new JsonSerializerOptions var options = new JsonSerializerOptions
{ {
WriteIndented = writeIndented, WriteIndented = writeIndented,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = typeInfoResolver
}; };
options.Converters.Add(new NullValueConverter()); options.Converters.Add(new NullValueConverter());
options.Converters.Add(new ByteStringConverter()); options.Converters.Add(new ByteStringConverter());
...@@ -38,12 +56,33 @@ internal static class JsonConverterHelper ...@@ -38,12 +56,33 @@ internal static class JsonConverterHelper
options.Converters.Add(new JsonConverterFactoryForEnum(context)); options.Converters.Add(new JsonConverterFactoryForEnum(context));
options.Converters.Add(new JsonConverterFactoryForWrappers(context)); options.Converters.Add(new JsonConverterFactoryForWrappers(context));
options.Converters.Add(new JsonConverterFactoryForWellKnownTypes(context)); options.Converters.Add(new JsonConverterFactoryForWellKnownTypes(context));
options.Converters.Add(new JsonConverterFactoryForMessage(context));
return options; return options;
} }
internal static Type GetFieldType(FieldDescriptor descriptor) internal static Type GetFieldType(FieldDescriptor descriptor)
{
if (descriptor.IsMap)
{
var mapFields = descriptor.MessageType.Fields.InFieldNumberOrder();
var keyField = mapFields[0];
var valueField = mapFields[1];
return typeof(MapField<,>).MakeGenericType(GetFieldTypeCore(keyField), GetFieldTypeCore(valueField));
}
else if (descriptor.IsRepeated)
{
var itemType = GetFieldTypeCore(descriptor);
return typeof(RepeatedField<>).MakeGenericType(itemType);
}
else
{
return GetFieldTypeCore(descriptor);
}
}
private static Type GetFieldTypeCore(FieldDescriptor descriptor)
{ {
switch (descriptor.FieldType) switch (descriptor.FieldType)
{ {
...@@ -85,6 +124,7 @@ internal static class JsonConverterHelper ...@@ -85,6 +124,7 @@ internal static class JsonConverterHelper
return t; return t;
} }
return descriptor.MessageType.ClrType; return descriptor.MessageType.ClrType;
default: default:
throw new ArgumentException("Invalid field type"); throw new ArgumentException("Invalid field type");
...@@ -124,7 +164,8 @@ internal static class JsonConverterHelper ...@@ -124,7 +164,8 @@ internal static class JsonConverterHelper
public static void PopulateList(ref Utf8JsonReader reader, JsonSerializerOptions options, IMessage message, FieldDescriptor fieldDescriptor) public static void PopulateList(ref Utf8JsonReader reader, JsonSerializerOptions options, IMessage message, FieldDescriptor fieldDescriptor)
{ {
var fieldType = GetFieldType(fieldDescriptor); var fieldType = GetFieldType(fieldDescriptor);
var repeatedFieldType = typeof(List<>).MakeGenericType(fieldType); var itemType = fieldType.GetGenericArguments()[0];
var repeatedFieldType = typeof(List<>).MakeGenericType(itemType);
var newValues = (IList)JsonSerializer.Deserialize(ref reader, repeatedFieldType, options)!; var newValues = (IList)JsonSerializer.Deserialize(ref reader, repeatedFieldType, options)!;
var existingValue = (IList)fieldDescriptor.Accessor.GetValue(message); var existingValue = (IList)fieldDescriptor.Accessor.GetValue(message);
...@@ -133,4 +174,67 @@ internal static class JsonConverterHelper ...@@ -133,4 +174,67 @@ internal static class JsonConverterHelper
existingValue.Add(item); existingValue.Add(item);
} }
} }
/// <summary>
/// Determines whether or not a field value should be serialized according to the field,
/// its value in the message, and the settings of this formatter.
/// </summary>
public static bool ShouldFormatFieldValue(IMessage message, FieldDescriptor field, object? value, bool formatDefaultValues) =>
field.HasPresence
// Fields that support presence *just* use that
? field.Accessor.HasValue(message)
// Otherwise, format if either we've been asked to format default values, or if it's
// not a default value anyway.
: formatDefaultValues || !IsDefaultValue(field, value);
private static bool IsDefaultValue(FieldDescriptor descriptor, object? value)
{
if (value == null)
{
return true;
}
if (descriptor.IsMap)
{
var dictionary = (IDictionary)value;
return dictionary.Count == 0;
}
if (descriptor.IsRepeated)
{
var list = (IList)value;
return list.Count == 0;
}
switch (descriptor.FieldType)
{
case FieldType.Bool:
return (bool)value == false;
case FieldType.Bytes:
return (ByteString)value == ByteString.Empty;
case FieldType.String:
return (string)value == string.Empty;
case FieldType.Double:
return (double)value == 0.0;
case FieldType.SInt32:
case FieldType.Int32:
case FieldType.SFixed32:
case FieldType.Enum:
return (int)value == 0;
case FieldType.Fixed32:
case FieldType.UInt32:
return (uint)value == 0;
case FieldType.Fixed64:
case FieldType.UInt64:
return (ulong)value == 0;
case FieldType.SFixed64:
case FieldType.Int64:
case FieldType.SInt64:
return (long)value == 0;
case FieldType.Float:
return (float)value == 0f;
case FieldType.Message:
case FieldType.Group: // Never expect to get this, but...
return value == null;
default:
throw new ArgumentException("Invalid field type");
}
}
} }
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
// This converter should be temporary until System.Text.Json supports overriding contacts.
// We want to eliminate this converter because System.Text.Json has to buffer content in converters.
internal sealed class MessageConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
private readonly Dictionary<string, FieldDescriptor> _jsonFieldMap;
public MessageConverter(JsonContext context) : base(context)
{
_jsonFieldMap = CreateJsonFieldMap((new TMessage()).Descriptor.Fields.InFieldNumberOrder());
}
public override TMessage Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
var message = new TMessage();
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException($"Unexpected JSON token: {reader.TokenType}");
}
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.EndObject:
return message;
case JsonTokenType.PropertyName:
if (_jsonFieldMap.TryGetValue(reader.GetString()!, out var fieldDescriptor))
{
if (fieldDescriptor.ContainingOneof != null)
{
if (fieldDescriptor.ContainingOneof.Accessor.GetCaseFieldDescriptor(message) != null)
{
throw new InvalidOperationException($"Multiple values specified for oneof {fieldDescriptor.ContainingOneof.Name}.");
}
}
if (fieldDescriptor.IsMap)
{
JsonConverterHelper.PopulateMap(ref reader, options, message, fieldDescriptor);
}
else if (fieldDescriptor.IsRepeated)
{
JsonConverterHelper.PopulateList(ref reader, options, message, fieldDescriptor);
}
else
{
var fieldType = JsonConverterHelper.GetFieldType(fieldDescriptor);
var propertyValue = JsonSerializer.Deserialize(ref reader, fieldType, options);
fieldDescriptor.Accessor.SetValue(message, propertyValue);
}
}
else
{
reader.Skip();
}
break;
case JsonTokenType.Comment:
// Ignore
break;
default:
throw new InvalidOperationException($"Unexpected JSON token: {reader.TokenType}");
}
}
throw new Exception();
}
public override void Write(
Utf8JsonWriter writer,
TMessage value,
JsonSerializerOptions options)
{
WriteMessage(writer, value, options);
}
private void WriteMessage(Utf8JsonWriter writer, IMessage message, JsonSerializerOptions options)
{
writer.WriteStartObject();
WriteMessageFields(writer, message, Context.Settings, options);
writer.WriteEndObject();
}
internal static void WriteMessageFields(Utf8JsonWriter writer, IMessage message, GrpcJsonSettings settings, JsonSerializerOptions options)
{
var fields = message.Descriptor.Fields;
foreach (var field in fields.InFieldNumberOrder())
{
var accessor = field.Accessor;
var value = accessor.GetValue(message);
if (!ShouldFormatFieldValue(message, field, value, !settings.IgnoreDefaultValues))
{
continue;
}
writer.WritePropertyName(accessor.Descriptor.JsonName);
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
private static Dictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
{
var map = new Dictionary<string, FieldDescriptor>();
foreach (var field in fields)
{
map[field.Name] = field;
map[field.JsonName] = field;
}
return new Dictionary<string, FieldDescriptor>(map);
}
/// <summary>
/// Determines whether or not a field value should be serialized according to the field,
/// its value in the message, and the settings of this formatter.
/// </summary>
private static bool ShouldFormatFieldValue(IMessage message, FieldDescriptor field, object value, bool formatDefaultValues) =>
field.HasPresence
// Fields that support presence *just* use that
? field.Accessor.HasValue(message)
// Otherwise, format if either we've been asked to format default values, or if it's
// not a default value anyway.
: formatDefaultValues || !IsDefaultValue(field, value);
private static bool IsDefaultValue(FieldDescriptor descriptor, object value)
{
if (descriptor.IsMap)
{
IDictionary dictionary = (IDictionary)value;
return dictionary.Count == 0;
}
if (descriptor.IsRepeated)
{
IList list = (IList)value;
return list.Count == 0;
}
switch (descriptor.FieldType)
{
case FieldType.Bool:
return (bool)value == false;
case FieldType.Bytes:
return (ByteString)value == ByteString.Empty;
case FieldType.String:
return (string)value == "";
case FieldType.Double:
return (double)value == 0.0;
case FieldType.SInt32:
case FieldType.Int32:
case FieldType.SFixed32:
case FieldType.Enum:
return (int)value == 0;
case FieldType.Fixed32:
case FieldType.UInt32:
return (uint)value == 0;
case FieldType.Fixed64:
case FieldType.UInt64:
return (ulong)value == 0;
case FieldType.SFixed64:
case FieldType.Int64:
case FieldType.SInt64:
return (long)value == 0;
case FieldType.Float:
return (float)value == 0f;
case FieldType.Message:
case FieldType.Group: // Never expect to get this, but...
return value == null;
default:
throw new ArgumentException("Invalid field type");
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Grpc.Shared;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class MessageTypeInfoResolver : IJsonTypeInfoResolver
{
private readonly JsonContext _context;
public MessageTypeInfoResolver(JsonContext context)
{
_context = context;
}
public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
{
if (!IsStandardMessage(type, out var messageDescriptor))
{
return null;
}
var typeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, options);
typeInfo.CreateObject = () => Activator.CreateInstance(type)!;
var fields = messageDescriptor.Fields.InFieldNumberOrder();
// The field map can have multiple entries for a property:
// 1. The JSON field name, e.g. firstName. This is used to serialize and deserialize JSON.
// 2. The original field name, e.g. first_name. This might be different. It is only used to deserialize JSON.
var mappings = CreateJsonFieldMap(fields);
foreach (var field in fields)
{
var propertyInfo = CreatePropertyInfo(typeInfo, field.JsonName, field, isSerializable: true);
typeInfo.Properties.Add(propertyInfo);
// We have a property for reading and writing the JSON name so remove from mappings.
mappings.Remove(field.JsonName);
}
// Fields have two mappings: the original field name and the camelcased JSON name.
// The JSON name can also be customized in proto with json_name option.
// Remaining mappings are for extra setter only properties.
foreach (var mapping in mappings)
{
var propertyInfo = CreatePropertyInfo(typeInfo, mapping.Key, mapping.Value, isSerializable: false);
typeInfo.Properties.Add(propertyInfo);
}
return typeInfo;
}
private static bool IsStandardMessage(Type type, [NotNullWhen(true)] out MessageDescriptor? messageDescriptor)
{
if (!typeof(IMessage).IsAssignableFrom(type))
{
messageDescriptor = null;
return false;
}
messageDescriptor = JsonConverterHelper.GetMessageDescriptor(type);
if (messageDescriptor == null)
{
return false;
}
// Wrappers and well known types are handled by converters.
if (ServiceDescriptorHelpers.IsWrapperType(messageDescriptor))
{
return false;
}
if (JsonConverterHelper.WellKnownTypeNames.ContainsKey(messageDescriptor.FullName))
{
return false;
}
return true;
}
private JsonPropertyInfo CreatePropertyInfo(JsonTypeInfo typeInfo, string name, FieldDescriptor field, bool isSerializable)
{
var propertyInfo = typeInfo.CreateJsonPropertyInfo(
JsonConverterHelper.GetFieldType(field),
name);
// Properties that don't have this flag set are only used to deserialize incoming JSON.
if (isSerializable)
{
propertyInfo.ShouldSerialize = (o, v) =>
{
return JsonConverterHelper.ShouldFormatFieldValue((IMessage)o, field, v, !_context.Settings.IgnoreDefaultValues);
};
propertyInfo.Get = (o) =>
{
return field.Accessor.GetValue((IMessage)o);
};
}
propertyInfo.Set = GetSetMethod(field);
return propertyInfo;
}
private static Action<object, object?> GetSetMethod(FieldDescriptor field)
{
if (field.IsMap)
{
return (o, v) =>
{
// The serializer creates a collection. Copy contents to collection on read-only property.
// An extra collection is being created here that's then thrown away.
// This will be removed once S.T.J supports deserializing onto a read-only property.
// https://github.com/dotnet/runtime/issues/30258
var existingValue = (IDictionary)field.Accessor.GetValue((IMessage)o);
foreach (DictionaryEntry item in (IDictionary)v!)
{
existingValue[item.Key] = item.Value;
}
};
}
if (field.IsRepeated)
{
return (o, v) =>
{
// The serializer creates a collection. Copy contents to collection on read-only property.
// An extra collection is being created here that's then thrown away.
// This will be removed once S.T.J supports deserializing onto a read-only property.
// https://github.com/dotnet/runtime/issues/30258
var existingValue = (IList)field.Accessor.GetValue((IMessage)o);
foreach (var item in (IList)v!)
{
existingValue.Add(item);
}
};
}
if (field.RealContainingOneof != null)
{
return (o, v) =>
{
var caseField = field.RealContainingOneof.Accessor.GetCaseFieldDescriptor((IMessage)o);
if (caseField != null)
{
throw new InvalidOperationException($"Multiple values specified for oneof {field.RealContainingOneof.Name}.");
}
field.Accessor.SetValue((IMessage)o, v);
};
}
return (o, v) =>
{
field.Accessor.SetValue((IMessage)o, v);
};
}
private static Dictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
{
var map = new Dictionary<string, FieldDescriptor>();
foreach (var field in fields)
{
map[field.Name] = field;
map[field.JsonName] = field;
}
return map;
}
}
...@@ -204,6 +204,7 @@ internal static class JsonRequestHelpers ...@@ -204,6 +204,7 @@ internal static class JsonRequestHelpers
// TODO: JsonSerializer currently doesn't support deserializing values onto an existing object or collection. // TODO: JsonSerializer currently doesn't support deserializing values onto an existing object or collection.
// Either update this to use new functionality in JsonSerializer or improve work-around perf. // Either update this to use new functionality in JsonSerializer or improve work-around perf.
type = JsonConverterHelper.GetFieldType(serverCallContext.DescriptorInfo.BodyFieldDescriptors.Last()); type = JsonConverterHelper.GetFieldType(serverCallContext.DescriptorInfo.BodyFieldDescriptors.Last());
type = type.GetGenericArguments()[0];
type = typeof(List<>).MakeGenericType(type); type = typeof(List<>).MakeGenericType(type);
GrpcServerLog.DeserializingMessage(serverCallContext.Logger, type); GrpcServerLog.DeserializingMessage(serverCallContext.Logger, type);
......
...@@ -20,6 +20,28 @@ public class JsonConverterReadTests ...@@ -20,6 +20,28 @@ public class JsonConverterReadTests
_output = output; _output = output;
} }
[Fact]
public void NonJsonName()
{
var json = @"{
""field_name"": ""A field name""
}";
var m = AssertReadJson<HelloRequest>(json);
Assert.Equal("A field name", m.FieldName);
}
[Fact]
public void JsonCustomizedName()
{
var json = @"{
""json_customized_name"": ""A field name""
}";
var m = AssertReadJson<HelloRequest>(json);
Assert.Equal("A field name", m.FieldName);
}
[Fact] [Fact]
public void ReadObjectProperties() public void ReadObjectProperties()
{ {
......
...@@ -21,6 +21,18 @@ public class JsonConverterWriteTests ...@@ -21,6 +21,18 @@ public class JsonConverterWriteTests
_output = output; _output = output;
} }
[Fact]
public void CustomizedName()
{
var helloRequest = new HelloRequest
{
FieldName = "A field name"
};
AssertWrittenJson(helloRequest,
new GrpcJsonSettings { IgnoreDefaultValues = true });
}
[Fact] [Fact]
public void NonAsciiString() public void NonAsciiString()
{ {
......
...@@ -180,6 +180,7 @@ message HelloRequest { ...@@ -180,6 +180,7 @@ message HelloRequest {
google.protobuf.ListValue list_value = 19; google.protobuf.ListValue list_value = 19;
google.protobuf.NullValue null_value = 20; google.protobuf.NullValue null_value = 20;
google.protobuf.FieldMask field_mask_value = 21; google.protobuf.FieldMask field_mask_value = 21;
string field_name = 22 [json_name="json_customized_name"];
} }
message HelloReply { message HelloReply {
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册