System.Text.Json中XmlElement(name, typeof(T))的等效实现咨询
问题描述
我有如下类:
public class Foo { [XmlElement("Bar", typeof(Bar))] [XmlElement("Pub", typeof(Pub))] public BaseBar Bar { get; set; } }
现在要从XML序列化迁移至System.Text.Json序列化,请问[XmlElement("Bar", typeof(Bar))]的等效实现是什么?
补充需求:
- 需要在属性级别定义映射逻辑,多个属性可以有各自独立的名称映射,比如:
public class Foo { [XmlElement("Bar", typeof(Bar))] [XmlElement("Pub", typeof(Pub))] public BaseBar Bar { get; set; } [XmlElement("Bar2", typeof(Bar))] [XmlElement("Pub2", typeof(Pub))] public BaseBar Bar2 { get; set; } }
- 现有方案会生成类似
{"Bar":{"$type":"Pub" …}}的输出,我期望的是当Bar属性为Pub类型时,序列化后的键名为"Pub",即输出格式为{"Pub":{…}}。
解决方案
System.Text.Json没有内置直接对应XmlElement该特性的功能,需要通过自定义Json转换器+自定义属性实现基于属性实际类型动态替换键名的逻辑,步骤如下:
1. 定义自定义属性,配置类型与键名的映射
创建自定义属性,用来标记每个属性对应的目标类型和序列化键名:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class JsonTypeKeyAttribute : Attribute { public Type TargetType { get; } public string KeyName { get; } public JsonTypeKeyAttribute(string keyName, Type targetType) { KeyName = keyName; TargetType = targetType; } }
2. 实现自定义JsonConverter
编写转换器,序列化时根据属性实际类型匹配对应键名;反序列化时根据键名匹配对应类型:
public class DynamicKeyConverter<T> : JsonConverter<T> { public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var doc = JsonDocument.ParseValue(ref reader); var root = doc.RootElement; var instance = Activator.CreateInstance<T>(); foreach (var prop in typeToConvert.GetProperties()) { var typeKeyAttrs = prop.GetCustomAttributes<JsonTypeKeyAttribute>().ToList(); if (!typeKeyAttrs.Any()) continue; foreach (var attr in typeKeyAttrs) { if (root.TryGetProperty(attr.KeyName, out var element)) { var value = JsonSerializer.Deserialize(element.GetRawText(), attr.TargetType, options); prop.SetValue(instance, value); break; } } } return instance; } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { writer.WriteStartObject(); var type = value.GetType(); foreach (var prop in type.GetProperties()) { var propValue = prop.GetValue(value); if (propValue == null) continue; var typeKeyAttrs = prop.GetCustomAttributes<JsonTypeKeyAttribute>().ToList(); if (!typeKeyAttrs.Any()) { // 无配置时使用默认属性名序列化 writer.WritePropertyName(prop.Name); JsonSerializer.Serialize(writer, propValue, options); continue; } // 匹配当前属性值类型对应的键名 var matchingAttr = typeKeyAttrs.FirstOrDefault(a => a.TargetType.IsAssignableFrom(propValue.GetType())); if (matchingAttr != null) { writer.WritePropertyName(matchingAttr.KeyName); JsonSerializer.Serialize(writer, propValue, propValue.GetType(), options); } } writer.WriteEndObject(); } }
3. 在类和属性上应用配置
替换原XmlElement属性为自定义属性,并给类指定自定义转换器:
[JsonConverter(typeof(DynamicKeyConverter<Foo>))] public class Foo { [JsonTypeKey("Bar", typeof(Bar))] [JsonTypeKey("Pub", typeof(Pub))] public BaseBar Bar { get; set; } [JsonTypeKey("Bar2", typeof(Bar))] [JsonTypeKey("Pub2", typeof(Pub))] public BaseBar Bar2 { get; set; } } // 基础类和派生类示例 public abstract class BaseBar { } public class Bar : BaseBar { } public class Pub : BaseBar { }
4. 测试序列化效果
执行序列化代码:
var foo = new Foo { Bar = new Pub(), Bar2 = new Bar() }; var json = JsonSerializer.Serialize(foo, new JsonSerializerOptions { WriteIndented = true }); Console.WriteLine(json);
输出结果符合预期:
{ "Pub": {}, "Bar2": {} }
注意事项
- 转换器包含反序列化逻辑,可根据键名匹配对应类型完成反序列化
- 属性值为
null时会被跳过,不生成对应键值对 - 未配置
JsonTypeKeyAttribute的属性,会使用默认属性名进行序列化
内容的提问来源于stack exchange,提问作者Rand Random
相关产品推荐
相关产品推荐

