System.Text.Json无法反序列化带私有getter的IEnumerable属性的原因分析
这个问题我之前踩过同款坑,其实是System.Text.Json对集合类型和非集合类型的反序列化逻辑差异导致的,我给你掰扯清楚:
首先先明确你的属性定义细节:你写的public BusinessEventType EventType { private get; set; } 其实是「属性本身是public,getter是private,setter是public」(C#里如果属性访问器没写修饰符,默认和属性的访问级别一致)。所以EventType、EventDate、People这三个属性的setter都是public,差异只在getter的权限,但前两个能正常赋值,唯独People不行——核心原因就在于People是集合类型(IEnumerable
1. 非集合类型的反序列化逻辑(为什么EventType、EventDate正常)
对于值类型(比如DateTime)或者自定义的非集合引用类型(比如你的BusinessEventType),System.Text.Json的反序列化逻辑很直接:只要属性的setter是public,不管getter的权限,它都会直接调用setter把反序列化后的对象/值硬塞给属性。
所以你的EventType和EventDate虽然getter是private,但setter是public,序列化器完全可以绕开getter,直接通过setter赋值,自然能正常工作。
2. 集合类型的反序列化逻辑(为什么People会null)
而集合类型(包括IEnumerable
System.Text.Json不会直接调用setter把整个集合对象丢给属性,而是会先尝试通过getter获取当前属性的集合实例:
- 如果能拿到实例(而且这个实例是可修改的集合,比如List
),就直接往里面加反序列化出来的元素; - 如果getter访问不了(比如你这里是private get),或者当前实例是null,就算序列化器创建了新的集合实例(比如给IEnumerable
默认创建List ),也没法完成后续操作——因为它需要通过getter确认集合的状态,private getter直接把路堵死了,最后只能跳过这个属性,让它保持为null。
这就是为什么你把People的getter改成public后就正常了:序列化器能通过getter访问属性,顺利完成集合的创建、填充,再通过public setter把最终的集合实例赋值好。
不想暴露getter的解决办法
如果你不想把People的getter改成public,有两个简单的方案:
- 方案一:加
[JsonInclude]特性:这个特性会告诉System.Text.Json允许访问private的getter/setter,这样即使getter是private,序列化器也能正常处理集合反序列化:[JsonInclude] public IEnumerable<Person> People { private get; set; } - 方案二:用具体集合类型并初始化:把People的类型改成List
这类具体的可修改集合,再初始化它,配合 [JsonInclude]就能正常工作,还能避免null:[JsonInclude] public List<Person> People { private get; set; } = new List<Person>();
内容来源于stack exchange

