如何实现序列化含Age属性、反序列化忽略它?现有方案存弊端
针对序列化需求与缓存键生成的优化方案
一、解决序列化/反序列化的优雅方案(无需混用两个序列化库)
你的核心需求是反序列化时忽略Age,序列化时保留Age,完全可以只用System.Text.Json实现,不用同时依赖Newtonsoft:
方法1:自定义Json转换器
写一个针对Request类的转换器,精准控制序列化和反序列化行为:
public class RequestConverter : JsonConverter<Request> { public override Request Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var request = new Request(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.PropertyName) { var propName = reader.GetString(); reader.Read(); if (propName.Equals(nameof(Request.Name), StringComparison.OrdinalIgnoreCase)) { request.Name = reader.GetString(); } // 直接跳过Age字段的反序列化 } else if (reader.TokenType == JsonTokenType.EndObject) { break; } } return request; } public override void Write(Utf8JsonWriter writer, Request value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString(nameof(Request.Name), value.Name); writer.WriteNumber(nameof(Request.Age), value.Age); writer.WriteEndObject(); } }
使用时给序列化/反序列化指定对应的配置:
var options = new JsonSerializerOptions(); options.Converters.Add(new RequestConverter()); // 反序列化(自动忽略Age) var request = JsonSerializer.Deserialize<Request>(jsonString, options); // 序列化(保留Age) var serialized = JsonSerializer.Serialize(request, options);
方法2:通过元数据配置实现差异化处理
不用写转换器,直接修改JsonSerializerOptions的元数据规则:
// 序列化用默认配置,保留所有属性 var serializeOptions = new JsonSerializerOptions(); // 反序列化配置:忽略Age字段 var deserializeOptions = new JsonSerializerOptions(); deserializeOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { typeInfo => { if (typeInfo.Type == typeof(Request)) { var ageProp = typeInfo.Properties.First(p => p.Name == nameof(Request.Age)); ageProp.IsIgnored = true; } } } }; // 反序列化时用deserializeOptions,序列化用serializeOptions即可
二、缓存键生成的优化方案
你的缓存需求是相同请求内容命中同一缓存,以下几个方案比双序列化器更靠谱:
1. 改用Record类型实现值相等判断
C# 9+的Record默认实现了值相等比较——只要属性值完全相同,两个Record实例就会被判定为相等。把Request改成Record:
public record Request { public string Name { get; set; } [JsonIgnore] public int Age { get; set; } }
此时直接用Request实例作为缓存键即可,无需序列化。测试验证:
var req1 = new Request { Name = "Alice", Age = 30 }; var req2 = new Request { Name = "Alice", Age = 30 }; Console.WriteLine(req1.Equals(req2)); // 输出True
主流缓存框架(如MemoryCache、IDistributedCache)都支持基于值比较的键匹配,完全满足需求。
2. 定义业务专属DTO生成缓存键
既然外部服务只关心影响结果的请求字段(看起来Age不影响外部服务响应),可以专门定义一个用于和外部服务交互的DTO:
public class ExternalRequest { public string Name { get; set; } }
控制器接收Request后,映射为ExternalRequest发送给外部服务,缓存键直接用ExternalRequest的序列化字符串或实例即可。这样彻底避免了Age字段对缓存的干扰,逻辑更清晰。
3. 安全高效的哈希缓存键
如果担心序列化性能,或者必须用字符串键,可以基于业务关键字段生成哈希:
public string GenerateCacheKey(Request request) { // 只使用影响外部服务响应的字段拼接成原始键内容 var keyContent = $"Name:{request.Name}"; // 用SHA256生成哈希,碰撞概率极低,性能损失可接受 using var sha256 = SHA256.Create(); var bytes = Encoding.UTF8.GetBytes(keyContent); var hashBytes = sha256.ComputeHash(bytes); return Convert.ToBase64String(hashBytes); }
MD5在业务场景中碰撞概率几乎为0,如果追求极致性能也可以用MD5替代SHA256。
三、综合最优方案
结合你的场景,推荐优先采用:
- 将
Request改为Record类型,直接用实例作为缓存键,省去序列化步骤 - 用
System.Text.Json的元数据配置实现反序列化忽略Age、序列化保留Age的需求 - 若
Age确实不影响外部服务响应,定义ExternalRequestDTO,进一步简化缓存逻辑
内容的提问来源于stack exchange,提问作者AnatolyBelanov
相关产品推荐
相关产品推荐

