使用System.Text.Json源生成功能时,如何添加序列化修饰器实现属性按字母序排序?
使用System.Text.Json源生成功能时,如何添加序列化修饰器实现属性按字母序排序?
我完全懂你现在的困扰——之前靠反射模式里的TypeInfoResolver修饰器轻松实现了属性排序,结果转源生成配合NativeAOT/修剪的时候,发现这套逻辑完全插不进去,还踩了好几个坑对吧?别慌,确实不用重写整个源生成器就能解决,我来给你捋捋可行的方案。
先说说为什么你之前的尝试卡壳了
JsonSourceGenerationOptions确实没开放修饰器入口:源生成的核心配置都是编译时确定的,属性里没留加修饰器的口子很正常。- 循环依赖的死胡同:你想先用Options初始化Context,再用Context修改Options,这本身就是鸡生蛋的问题——源生成的
JsonSerializerContext.Default是编译时静态生成的,根本不需要你手动初始化,之前绕错方向了。 - 只读的
JsonTypeInfo:源生成的TypeInfo是编译时固化的,标记为只读,防止修改后破坏缓存,所以直接改肯定报错。
可行方案:自定义IJsonTypeInfoResolver组合源生成逻辑和排序
核心思路是:不碰源生成的只读TypeInfo,而是用自定义Resolver把源生成的Resolver和你的排序逻辑“拼”起来——先从源生成器拿TypeInfo,复制成可写的副本,排序后再用这个副本。既保留源生成的性能,又实现了排序,还完全支持修剪和NativeAOT。
步骤1:保留你的源生成Context
先继续用原来的源生成配置,比如:
[JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(User))] [JsonSerializable(typeof(Order))] // 把你需要序列化的类型都加上 public partial class AppJsonContext : JsonSerializerContext { }
步骤2:复用你的排序修饰逻辑
原来的AlphabetizeProperties可以直接用,不用改:
private static void AlphabetizeProperties(JsonTypeInfo info) { if (info.Kind != JsonTypeInfoKind.Object) return; var sortedProperties = info.Properties .OrderBy(p => p.Name, StringComparer.Ordinal) .ToList(); info.Properties.Clear(); for (int i = 0; i < sortedProperties.Count; i++) { sortedProperties[i].Order = i; info.Properties.Add(sortedProperties[i]); } }
步骤3:实现自定义Resolver组合逻辑
写一个轻量的自定义Resolver,它会先从源生成Context拿TypeInfo,复制成可写副本后再排序:
public class SortingTypeInfoResolver : IJsonTypeInfoResolver { private readonly IJsonTypeInfoResolver _sourceGenResolver; public SortingTypeInfoResolver(IJsonTypeInfoResolver sourceGenResolver) { _sourceGenResolver = sourceGenResolver; } public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) { // 从源生成器拿只读的TypeInfo var generatedTypeInfo = _sourceGenResolver.GetTypeInfo(type, options); if (generatedTypeInfo == null || generatedTypeInfo.Kind != JsonTypeInfoKind.Object) { return generatedTypeInfo; } // 复制成可写的TypeInfo副本 var writableTypeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, options); foreach (var prop in generatedTypeInfo.Properties) { writableTypeInfo.Properties.Add(prop); } // 应用排序 AlphabetizeProperties(writableTypeInfo); return writableTypeInfo; } }
步骤4:创建全局的JsonSerializerOptions
最后把自定义Resolver挂到Options上,全局复用:
public static class JsonSettings { public static readonly JsonSerializerOptions Options = new() { WriteIndented = true, // 组合源生成Resolver和排序逻辑 TypeInfoResolver = new SortingTypeInfoResolver(AppJsonContext.Default) }; }
步骤5:正常序列化
直接用这个Options序列化就好,属性会自动按字母序排列:
var user = new User { Name = "Alice", Age = 30, Email = "alice@example.com" }; var json = JsonSerializer.Serialize(user, JsonSettings.Options); // 输出的JSON属性会按 Age → Email → Name 的顺序排列
为什么这个方案能行?
- 无反射、全源生成:所有TypeInfo都是编译时生成的,自定义Resolver只是做了复制和排序,没有用反射,完全支持修剪和NativeAOT。
- 无循环依赖:直接用编译好的
AppJsonContext.Default(静态实例)初始化Resolver,根本不需要手动创建Context,自然没有循环依赖问题。 - 逻辑兼容:排序逻辑和你之前的完全一致,不会引入新的行为差异。
如果想更简洁,还可以把自定义Resolver改成通用的委托式实现,省得单独写类:
// 通用的委托式Resolver public class DelegatingJsonResolver : IJsonTypeInfoResolver { private readonly IJsonTypeInfoResolver _innerResolver; public Func<JsonTypeInfo, JsonTypeInfo>? PostProcess { get; set; } public DelegatingJsonResolver(IJsonTypeInfoResolver innerResolver) { _innerResolver = innerResolver; } public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) { var info = _innerResolver.GetTypeInfo(type, options); return info != null ? PostProcess?.Invoke(info) ?? info : null; } } // 简化Options初始化 public static class JsonSettings { public static readonly JsonSerializerOptions Options = new() { WriteIndented = true, TypeInfoResolver = new DelegatingJsonResolver(AppJsonContext.Default) { PostProcess = info => { if (info.Kind != JsonTypeInfoKind.Object) return info; var writable = JsonTypeInfo.CreateJsonTypeInfo(info.Type, Options); foreach (var prop in info.Properties) writable.Properties.Add(prop); AlphabetizeProperties(writable); return writable; } } }; }
内容来源于stack exchange
相关产品推荐
相关产品推荐

