.NET 4.5下如何用Newtonsoft.Json实现灵活嵌套JSON的存储与解析
问题根源定位
当前流程存在双重序列化转义问题:
- 第一次序列化:将
ExpiryTriggerJson实例转为JSON字符串,赋值给TriggerJson.Trigger属性 - 第二次序列化:将整个
List<TriggerJson>序列化为最终存储的大JSON时,会把Trigger属性的string值当作普通文本处理,自动转义内部所有双引号,最终形成带大量冗余转义符的输出
反序列化阶段的报错本质是:外层JSON解析完成后,obj.Trigger节点是纯文本字符串类型(存的是带转义的JSON片段),不是结构化的JSON对象节点,直接调用ToObject<T>()自然无法匹配ExpiryTriggerJson的结构。
可落地实现方案
完全适配.NET 4.5运行环境,同时保留Trigger为string类型、支持按TriggerType动态匹配结构的灵活存储需求:
方案1:从根源避免双层转义(推荐,适配新数据写入)
因为Trigger字段是string类型,默认序列化时会被当作普通字符串值处理,需要通过自定义转换器让它在序列化时以原始JSON片段的形式写入,而非转义字符串:
- 编写适配旧框架的原始JSON转换器:
public class RawJsonStringConverter : JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var jsonStr = value as string; if (!string.IsNullOrWhiteSpace(jsonStr)) { // 直接将字符串内容作为原始JSON片段写入,不做转义 writer.WriteRawValue(jsonStr); } else { writer.WriteNull(); } } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 读取时把结构化JSON节点转回字符串,保留灵活存储特性 if (reader.TokenType == JsonToken.Null) return null; return JRaw.Create(reader).ToString(); } public override bool CanConvert(Type objectType) { return objectType == typeof(string); } }
- 给
TriggerJson的Trigger属性标记转换器:
public class TriggerJson { public string TriggerType { get; set; } public string ConfiguredBy { get; set; } // 标记自定义转换器,避免双层转义 [JsonConverter(typeof(RawJsonStringConverter))] public string Trigger { get; set; } }
调整完成后,原有的写入逻辑完全不需要修改,最终生成的存储JSON会变为结构化的嵌套格式,无冗余转义符:
[ { "TriggerType": "ExpiryTrigger", "ConfiguredBy": "acd1ac353ac44e078aaef8ce6479a4c6", "Trigger": { "ActionType": "CreateReminderTask", "Recipients": [ { "Id": "70050a95-f31b-41b7-9b49-0688fa76dba5", "Name": "blah blah", "IsTag": false } ] } } ]
此时原有的反序列化逻辑obj.Trigger.ToObject<ExpiryTriggerJson>()可以正常运行,因为obj.Trigger已经是结构化的JSON节点,不再是纯文本字符串。
方案2:兼容存量转义格式数据
如果系统已经存在大量旧逻辑写入的带转义的存量数据,不需要调整序列化逻辑,只需修改反序列化阶段的节点判断逻辑即可:
JArray jsonObj = JArray.Parse(triggersJson); foreach (dynamic obj in jsonObj) { if (obj.TriggerType == ExpiryTriggerType) { ExpiryTriggerJson triggerData; // 判断Trigger节点类型,分别处理新旧格式 if (obj.Trigger.Type == JTokenType.String) { // 旧格式:取出被转义的字符串,先反序列化为目标类型 var innerJson = (string)obj.Trigger; triggerData = JsonConvert.DeserializeObject<ExpiryTriggerJson>(innerJson); } else { // 新格式:结构化节点直接转换 triggerData = obj.Trigger.ToObject<ExpiryTriggerJson>(); } } }
实现注意事项
- 以上实现完全兼容.NET 4.5版本配套的Newtonsoft.Json类库,无需升级运行时框架
- 整个实现过程没有为
TriggerJson硬编码嵌套的触发结构,后续新增其他触发类型时,只需传入对应结构的JSON字符串给Trigger字段即可,完全符合灵活JSON存储的设计目标 - 可在
RawJsonStringConverter的写入逻辑中增加JSON合法性校验,避免非法格式的字符串导致序列化失败
内容的提问来源于stack exchange,提问作者Riz
相关产品推荐
相关产品推荐

