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

C#如何通过自定义特性指定Json序列化的属性子集

System.Text.Json按自定义特性筛选属性序列化实现方案

你可以通过自定义System.Text.Json的类型元数据解析逻辑实现需求,不需要修改现有业务类结构,支持灵活传入多个特性类型指定要序列化的属性分组,性能和原生序列化保持一致。

前置:自定义特性定义

你现有的分组特性只需要确保标记为可作用于属性即可,示例定义如下,可直接复用你已有的代码:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CustomNameAttribute : Attribute { }

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CustomAddressAttribute : Attribute { }

你给出的MyClass类不需要做任何调整,直接使用即可。

核心实现:序列化扩展方法

通过给JsonSerializerOptions配置自定义的类型信息修改器,在序列化前过滤掉未标记目标特性的属性,封装为通用扩展方法后即可实现你期望的调用效果:

using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

public static class JsonAttributeFilterExtensions
{
    /// <summary>
    /// 仅序列化标记了指定特性的属性
    /// </summary>
    /// <param name="instance">待序列化对象实例</param>
    /// <param name="includeAttributeTypes">需要纳入序列化范围的属性标记特性类型集合</param>
    /// <param name="baseOptions">基础序列化配置,会复制配置避免修改全局/传入的原始配置</param>
    /// <returns>筛选属性后的JSON字符串</returns>
    public static string SerializeWithAttributeFilter<T>(
        this T instance, 
        Type[] includeAttributeTypes, 
        JsonSerializerOptions? baseOptions = null)
    {
        var options = baseOptions == null 
            ? new JsonSerializerOptions() 
            : new JsonSerializerOptions(baseOptions);

        options.TypeInfoResolver = new DefaultJsonTypeInfoResolver
        {
            Modifiers = { ApplyAttributeFilter(includeAttributeTypes.ToHashSet()) }
        };

        return JsonSerializer.Serialize(instance, options);
    }

    private static Action<JsonTypeInfo> ApplyAttributeFilter(HashSet<Type> includeAttributes)
    {
        return typeInfo =>
        {
            // 非对象类型(值类型、集合、字符串等)不做属性过滤
            if (typeInfo.Kind != JsonTypeInfoKind.Object) return;

            // 倒序遍历移除不符合要求的属性,避免遍历过程中索引变化问题
            for (int i = typeInfo.Properties.Count - 1; i >= 0; i--)
            {
                JsonPropertyInfo prop = typeInfo.Properties[i];
                if (prop.AttributeProvider == null)
                {
                    typeInfo.Properties.RemoveAt(i);
                    continue;
                }

                // 检查当前属性是否标记了任意一个目标特性
                bool hasMatch = prop.AttributeProvider
                    .GetCustomAttributes(inherit: false)
                    .Any(attr => includeAttributes.Contains(attr.GetType()));

                if (!hasMatch)
                {
                    typeInfo.Properties.RemoveAt(i);
                }
            }
        };
    }
}

调用示例

var myClassInstance = new MyClass
{
    UserFirstName = "王",
    UserLastName = "五",
    UserAddressLine1 = "解放路88号",
    UserAddressLine2 = "幸福小区3栋2单元"
};

// 1. 仅序列化姓名分组属性(标记CustomNameAttribute的属性)
string nameGroupJson = myClassInstance.SerializeWithAttributeFilter(new[] { typeof(CustomNameAttribute) });
// 输出:{"UserFirstName":"王","UserLastName":"五"}

// 2. 仅序列化地址分组属性(标记CustomAddressAttribute的属性)
string addressGroupJson = myClassInstance.SerializeWithAttributeFilter(new[] { typeof(CustomAddressAttribute) });
// 输出:{"UserAddressLine1":"解放路88号","UserAddressLine2":"幸福小区3栋2单元"}

// 3. 同时序列化姓名+地址两个分组的属性
string allGroupJson = myClassInstance.SerializeWithAttributeFilter(new[] { typeof(CustomNameAttribute), typeof(CustomAddressAttribute) });
// 输出包含全部4个属性

补充说明

  • 该实现兼容.NET 6及以上版本,不需要引入第三方序列化组件
  • 如果你使用Newtonsoft.Json,核心逻辑一致,通过自定义DefaultContractResolver重写属性筛选规则即可实现相同效果
  • 支持属性标记多个分组特性的场景,只要属性标记的任意一个特性在传入的包含列表中,就会被序列化
  • 如果需要实现排除指定特性属性的效果,只需要调整筛选逻辑,移除标记了传入特性的属性即可

内容的提问来源于stack exchange,提问作者Gabe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:57:12