多JSON文件反序列化及多关联关系映射优化方案咨询
多JSON文件反序列化与关联映射优化方案
场景与问题
目标对象模型
public class Foo { public string Id { get; set; } public string Name { get; set; } } public class Bar { public string Id { get; set; } public Foo Foo { get; set; } public DateTime At { get; set; } } public class Baz { public string Id { get; set; } public DateTime At { get; set; } public List<Bar> Bars { get; set; } = new List<Bar>(); } public class Qux { public string Id { get; set; } public string Name { get; set; } public List<Baz> Baz { get; set; } = new List<Baz>(); }
待处理的JSON文件
Foo.json
[ { "Id": "1", "Name": "AAA" }, { "Id": "2", "Name": "BBB" } ]
Bar.json
[ { "Id": "1", "FooId": "1", "BazId": "1", "At": "2022-01-01T10:00:00" }, { "Id": "2", "FooId": "2", "BazId": "1", "At": "2022-01-02T10:00:00" } ]
Baz.json
[ { "Id": "1", "Quxd": "1", "At": "2022-01-01T10:00:00" }, { "Id": "2", "QuxId": "1", "At": "2022-01-02T10:00:00" } ]
注:Baz.json中第一个对象的关联字段应为
QuxId,推测是笔误,需兼容处理
Qux.json
[ { "Id": "1", "Name": "CCC" }, { "Id": "2", "Name": "DDD" } ]
当前痛点
JSON设计存在缺陷但无法修改,现有方案是为每个JSON创建对应DTO类,反序列化后通过嵌套循环映射关联关系,效率低下,需要更优实现方式。
优化解决方案
方案1:字典索引+批量映射(推荐)
核心思路是用字典以Id为键构建全局索引,将嵌套循环的O(n²)时间复杂度降至接近O(n),大幅提升映射效率。
步骤1:定义匹配JSON的DTO类
// Foo和Qux的DTO与目标类结构一致,可直接复用原类 public class BarDto { public string Id { get; set; } public string FooId { get; set; } public string BazId { get; set; } public DateTime At { get; set; } } public class BazDto { public string Id { get; set; } public string QuxId { get; set; } public DateTime At { get; set; } // 兼容JSON中的笔误字段 public string Quxd { get; set; } }
步骤2:反序列化并构建索引字典
// 异步读取并反序列化所有JSON文件 var foos = JsonSerializer.Deserialize<List<Foo>>(await File.ReadAllTextAsync("Foo.json")); var barDtos = JsonSerializer.Deserialize<List<BarDto>>(await File.ReadAllTextAsync("Bar.json")); var bazDtos = JsonSerializer.Deserialize<List<BazDto>>(await File.ReadAllTextAsync("Baz.json")); var quxes = JsonSerializer.Deserialize<List<Qux>>(await File.ReadAllTextAsync("Qux.json")); // 构建O(1)查找的索引字典 var fooIndex = foos.ToDictionary(f => f.Id); var quxIndex = quxes.ToDictionary(q => q.Id); var bazIndex = new Dictionary<string, Baz>();
步骤3:批量完成关联映射
// 先初始化Baz并关联到对应的Qux foreach (var bazDto in bazDtos) { var baz = new Baz { Id = bazDto.Id, At = bazDto.At, Bars = new List<Bar>() }; bazIndex[bazDto.Id] = baz; // 兼容JSON中的笔误字段 var quxId = bazDto.QuxId ?? bazDto.Quxd; if (quxIndex.TryGetValue(quxId, out var targetQux)) { targetQux.Baz.Add(baz); } } // 再初始化Bar并关联到对应的Foo和Baz foreach (var barDto in barDtos) { var bar = new Bar { Id = barDto.Id, At = barDto.At, Foo = fooIndex[barDto.FooId] }; bazIndex[barDto.BazId].Bars.Add(bar); }
方案2:自定义JsonConverter,反序列化时直接关联
如果不想额外维护DTO类,可以自定义JSON转换器,在反序列化过程中直接利用已加载的索引完成关联,减少后续映射步骤。
示例:Bar类的自定义转换器
public class BarConverter : JsonConverter<Bar> { private readonly Dictionary<string, Foo> _fooLookup; private readonly Dictionary<string, Baz> _bazLookup; public BarConverter(Dictionary<string, Foo> fooLookup, Dictionary<string, Baz> bazLookup) { _fooLookup = fooLookup; _bazLookup = bazLookup; } public override Bar Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var jsonDoc = JsonDocument.ParseValue(ref reader); var root = jsonDoc.RootElement; var bar = new Bar { Id = root.GetProperty("Id").GetString(), At = root.GetProperty("At").GetDateTime(), Foo = _fooLookup[root.GetProperty("FooId").GetString()] }; // 直接关联到对应的Baz var bazId = root.GetProperty("BazId").GetString(); _bazLookup[bazId].Bars.Add(bar); return bar; } public override void Write(Utf8JsonWriter writer, Bar value, JsonSerializerOptions options) { throw new NotImplementedException("无需实现序列化逻辑"); } }
使用方式
// 先加载依赖的基础数据并构建索引 var foos = JsonSerializer.Deserialize<List<Foo>>(await File.ReadAllTextAsync("Foo.json")); var fooIndex = foos.ToDictionary(f => f.Id); var quxes = JsonSerializer.Deserialize<List<Qux>>(await File.ReadAllTextAsync("Qux.json")); var quxIndex = quxes.ToDictionary(q => q.Id); var bazDtos = JsonSerializer.Deserialize<List<BazDto>>(await File.ReadAllTextAsync("Baz.json")); var bazIndex = new Dictionary<string, Baz>(); foreach (var bazDto in bazDtos) { var baz = new Baz { Id = bazDto.Id, At = bazDto.At, Bars = new List<Bar>() }; bazIndex[bazDto.Id] = baz; var quxId = bazDto.QuxId ?? bazDto.Quxd; if (quxIndex.TryGetValue(quxId, out var targetQux)) { targetQux.Baz.Add(baz); } } // 配置转换器并反序列化Bar var serializerOptions = new JsonSerializerOptions(); serializerOptions.Converters.Add(new BarConverter(fooIndex, bazIndex)); var bars = JsonSerializer.Deserialize<List<Bar>>(await File.ReadAllTextAsync("Bar.json"), serializerOptions);
内容的提问来源于stack exchange,提问作者Ash
相关产品推荐
相关产品推荐

