如何在protobuf-net中使用JToken?解决序列化不支持问题
解决Protobuf-net序列化Dictionary<string, JToken>的异常问题
问题重现
模型定义:
[ProtoContract] public class Foo { [ProtoMember(1)] public Dictionary<string, JToken> Data { get; } = new(); }
执行序列化/深克隆时抛出异常:
未处理的异常。System.NotSupportedException: 不支持嵌套或锯齿状列表、数组和映射:Newtonsoft.Json.Linq.JToken
触发异常的示例代码:
var obj = new Foo { Data = { ["abc"] = JToken.Parse(@"{""a"":true}"), ["def"] = JToken.Parse(@"null"), ["ghi"] = JToken.Parse(@"true"), ["jkl"] = JToken.Parse(@"[{},12.3,'abc']"), } }; var clone = Serializer.DeepClone(obj); // 此处抛出异常 foreach (var (key, value) in clone.Data) { Console.WriteLine($"{key}: {value.ToString(Formatting.None)}"); }
解决方案
方法一:为JToken实现自定义Protobuf序列化器
Protobuf-net允许通过自定义序列化器处理非原生支持的类型,为JToken编写序列化逻辑,将其转换为Protobuf兼容格式:
public class JTokenSerializer : ISerializer<JToken> { public void Write(ref ProtoWriter state, JToken value) { var json = value.ToString(Formatting.None); ProtoWriter.WriteString(json, ref state); } public JToken Read(ref ProtoReader state) { var json = ProtoReader.ReadString(ref state); return JToken.Parse(json); } }
在程序启动时注册该序列化器:
RuntimeTypeModel.Default.Add(typeof(JToken), false).SetSerializer(new JTokenSerializer());
之后再执行深克隆或序列化操作即可正常运行。
方法二:将JToken转换为JSON字符串存储
修改模型,用Dictionary<string, string>存储JToken的JSON字符串形式,对外封装JToken的访问接口:
[ProtoContract] public class Foo { [ProtoMember(1)] private Dictionary<string, string> _data { get; } = new(); public Dictionary<string, JToken> Data { get => _data.ToDictionary(kv => kv.Key, kv => JToken.Parse(kv.Value)); set { _data.Clear(); foreach (var kv in value) { _data[kv.Key] = kv.Value.ToString(Formatting.None); } } } }
这种方式无需额外配置Protobuf-net,通过字段封装实现类型转换,缺点是读写时会有额外的序列化/反序列化开销。
方法三:改用Dynamic类型(限Protobuf-net v3+)
如果使用Protobuf-net v3及以上版本,可尝试将JToken替换为dynamic,Protobuf-net对动态类型有更好的兼容性:
[ProtoContract] public class Foo { [ProtoMember(1)] public Dictionary<string, dynamic> Data { get; } = new(); }
注意:这种方式需确保动态类型的结构符合Protobuf序列化规则,复杂嵌套结构可能仍存在兼容性问题。
内容的提问来源于stack exchange,提问作者Marc Gravell
相关产品推荐
相关产品推荐

