You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 16:00:59