如何在C#中使用System.Text.Json将属性序列化为独立JSON
实现拆分JSON序列化与延迟加载方案
1. 定义核心类结构
先调整类X,加入路径存储和懒加载逻辑,类A包含X类型的属性:
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; public class X { // 标记是否需要拆分存储 public bool SplitJson { get; set; } // 大型列表数据 public List<BigDataItem> LargeList { get; set; } = new(); // 存储拆分后的文件路径(序列化/反序列化时忽略) [JsonIgnore] public string? FilePath { get; set; } // 懒加载逻辑:首次访问时才从文件加载数据 private Lazy<List<BigDataItem>>? _lazyLargeList; [JsonIgnore] public List<BigDataItem> LazyLargeList { get { if (_lazyLargeList != null) return _lazyLargeList.Value; return LargeList; } } // 初始化懒加载逻辑(反序列化路径时调用) internal void InitializeLazyLoad() { if (!string.IsNullOrEmpty(FilePath)) { _lazyLargeList = new Lazy<List<BigDataItem>>(() => { var json = File.ReadAllText(FilePath); return JsonConvert.DeserializeObject<List<BigDataItem>>(json) ?? new List<BigDataItem>(); }); // 清空内存列表,强制使用懒加载版本 LargeList = null!; } } } public class BigDataItem { // 示例大型数据项属性 public int Id { get; set; } public string Content { get; set; } = string.Empty; } public class A { [JsonConverter(typeof(XSplitConverter))] public X? X1 { get; set; } [JsonConverter(typeof(XSplitConverter))] public X? X2 { get; set; } [JsonConverter(typeof(XSplitConverter))] public X? X3 { get; set; } }
2. 自定义JSON转换器实现拆分逻辑
编写XSplitConverter处理X类型的序列化/反序列化,同时管理拆分文件的存储:
public class XSplitConverter : JsonConverter<X> { // 拆分文件的基础存储目录 private readonly string _baseDirectory; public XSplitConverter() : this(Directory.GetCurrentDirectory()) { } public XSplitConverter(string baseDirectory) { _baseDirectory = baseDirectory; Directory.CreateDirectory(_baseDirectory); // 确保目录存在 } public override void WriteJson(JsonWriter writer, X? value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); return; } if (value.SplitJson) { // 生成唯一文件名(可根据需求替换为更具可读性的命名规则) var fileName = $"X_{Guid.NewGuid()}.json"; var fullPath = Path.Combine(_baseDirectory, fileName); // 将X的大型列表单独序列化到文件 var listJson = JsonConvert.SerializeObject(value.LargeList, Formatting.Indented); File.WriteAllText(fullPath, listJson); // 序列化主文件时写入相对路径 writer.WriteValue(Path.GetRelativePath(_baseDirectory, fullPath)); // 保存路径到X实例(用于后续操作) value.FilePath = fullPath; } else { // 无需拆分时正常序列化整个X对象 serializer.Serialize(writer, value); } } public override X? ReadJson(JsonReader reader, Type objectType, X? existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; // 判断当前值是路径字符串还是完整X对象 if (reader.TokenType == JsonToken.String) { var relativePath = reader.Value.ToString(); var fullPath = Path.Combine(_baseDirectory, relativePath); var x = new X { SplitJson = true, FilePath = fullPath }; x.InitializeLazyLoad(); // 初始化懒加载 return x; } else { // 正常反序列化X对象 var x = serializer.Deserialize<X>(reader); x?.InitializeLazyLoad(); // 兼容非拆分场景 return x; } } }
3. 使用示例
序列化类A
// 构造测试数据 var a = new A { X1 = new X { SplitJson = true, LargeList = Enumerable.Range(1, 10000).Select(i => new BigDataItem { Id = i, Content = $"Item {i}" }).ToList() }, X2 = new X { SplitJson = false, LargeList = Enumerable.Range(1, 100).Select(i => new BigDataItem { Id = i, Content = $"Small Item {i}" }).ToList() }, X3 = null }; // 序列化A到主文件 var serializerSettings = new JsonSerializerSettings { Converters = { new XSplitConverter(@"C:\SplitJsonStorage") }, // 指定存储目录 Formatting = Formatting.Indented }; var aJson = JsonConvert.SerializeObject(a, serializerSettings); File.WriteAllText(@"C:\SplitJsonStorage\A.json", aJson);
反序列化类A并延迟加载
// 反序列化主文件 var deserializedA = JsonConvert.DeserializeObject<A>(File.ReadAllText(@"C:\SplitJsonStorage\A.json"), serializerSettings); // 访问X1的LazyLargeList时才会加载对应拆分文件 var x1Items = deserializedA?.X1?.LazyLargeList; Console.WriteLine($"X1加载了 {x1Items?.Count} 条数据"); // X2未拆分,直接读取内存数据 var x2Items = deserializedA?.X2?.LazyLargeList; Console.WriteLine($"X2加载了 {x2Items?.Count} 条数据");
关键注意事项
- 路径一致性:序列化和反序列化必须使用相同的基础目录,避免路径解析错误。
- 线程安全:默认
Lazy<T>是线程安全的,若需自定义线程行为可调整LazyThreadSafetyMode参数。 - 异常处理:实际生产环境中需添加文件读写、JSON反序列化的异常捕获逻辑,防止程序崩溃。
- 文件名规则:示例用Guid生成唯一文件名,也可根据类A的标识、属性名等生成更易识别的文件名。
内容的提问来源于stack exchange,提问作者ShankarBUS
相关产品推荐
相关产品推荐

