使用JsonPropertyName匹配构造函数参数失效问题排查
问题描述
要给现有类库添加System.Text.Json序列化功能,目标类型的构造函数参数与只读属性/字段名称不匹配,且不想修改公共成员或参数的名称。以Person结构体为例:
原结构体定义:
public struct Person { public Person(string Name) { FirstName = Name; } public string FirstName { get; } }
尝试给属性加[JsonPropertyName]、构造函数加[JsonConstructor]来适配JSON键名:
public struct Person { [JsonConstructor] public Person(string Name) { FirstName = Name; } [JsonPropertyName("Name")] public string FirstName { get; } }
此时序列化输出正常:
{ "Name": "Joe" }
但反序列化时抛出错误:
'Each parameter in the deserialization constructor on type 'Person' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.'
原因是System.Text.Json的构造函数参数绑定逻辑是:默认匹配类型的属性/字段名称,而非JSON键名。即使加了[JsonConstructor],也只会按参数名找对应的属性/字段,不会关联JsonPropertyName指定的JSON键。
解决方案
方案1:给构造函数参数添加[JsonPropertyName](.NET 5及以上适用)
从.NET 5开始,System.Text.Json支持给构造函数参数直接标记[JsonPropertyName],明确参数对应的JSON键名,无需修改参数或属性的名称:
public struct Person { [JsonConstructor] public Person([JsonPropertyName("Name")] string Name) { FirstName = Name; } [JsonPropertyName("Name")] public string FirstName { get; } }
这样序列化时属性会输出为"Name",反序列化时也能正确将JSON的"Name"值绑定到构造函数的Name参数,完美匹配需求。
方案2:自定义JsonConverter(兼容.NET Core 3.x及更早版本)
如果项目基于不支持参数标记[JsonPropertyName]的旧版本.NET,可以编写自定义转换器,完全控制序列化和反序列化逻辑,且无需修改原Person结构体:
首先实现转换器:
public class PersonConverter : JsonConverter<Person> { public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("无效的JSON对象格式"); string name = null; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) break; if (reader.TokenType == JsonTokenType.PropertyName) { string propName = reader.GetString(); reader.Read(); if (propName.Equals("Name", StringComparison.OrdinalIgnoreCase)) name = reader.GetString(); } } return new Person(name); } public override void Write(Utf8JsonWriter writer, Person value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString("Name", value.FirstName); writer.WriteEndObject(); } }
使用时在序列化配置中添加转换器:
var options = new JsonSerializerOptions(); options.Converters.Add(new PersonConverter()); // 反序列化示例 string json = "{\"Name\":\"Joe\"}"; Person person = JsonSerializer.Deserialize<Person>(json, options); // 序列化示例 string serialized = JsonSerializer.Serialize(person, options);
这个方案完全不修改原结构体,通过外部转换器实现JSON与类型的映射。
内容的提问来源于stack exchange,提问作者Soggy

