如何解决使用 DataContractSerializer 和 XmlDictionaryWriter 序列化 JObject 后崩溃
我必须使用 DataContractSerializer 序列化 Newtonsoft JObject,它会因堆栈溢出而崩溃。 如何使它工作? 我的代码是。
var serializer = new DataContractSerializer(typeof(JObject));
MemoryStream stream1 = new MemoryStream();
var writer = XmlDictionaryWriter.CreateBinaryWriter(stream1);
var obj = new JObject();
serializer.WriteObject(writer,obj);
writer.Flush();
以下示例使用 ISerializationSurrogateProvider
功能将 JObject 转换为通用类型。它会因堆栈溢出而崩溃。
using System;
using System.IO;
using Newtonsoft.Json.Linq;
using System.Runtime.Serialization;
using System.Xml;
class Program
{
[DataContract(Name = "JTokenReference",Namespace = "urn:actors")]
[Serializable]
public sealed class JTokenReference
{
public JTokenReference()
{
}
[DataMember(Name = "JType",Order = 0,Isrequired = true)]
public JTokenType JType { get; set; }
[DataMember(Name = "Value",Order = 1,Isrequired = true)]
public string Value { get; set; }
public static JTokenReference From(JToken jt)
{
if (jt == null)
{
return null;
}
return new JTokenReference()
{
Value = jt.ToString(),JType = jt.Type
};
}
public object To()
{
switch (JType)
{
case JTokenType.Object:
{
return JObject.Parse(Value);
}
case JTokenType.Array:
{
return JArray.Parse(Value);
}
default:
{
return JToken.Parse(Value);
}
}
}
}
internal class ActorDataContractSurrogate : ISerializationSurrogateProvider
{
public static readonly ISerializationSurrogateProvider Instance = new ActorDataContractSurrogate();
public Type GetSurrogateType(Type type)
{
if (typeof(JToken).IsAssignableFrom(type))
{
return typeof(JTokenReference);
}
return type;
}
public object GetobjectToSerialize(object obj,Type targettype)
{
if (obj == null)
{
return null;
}
else if (obj is JToken jt)
{
return JTokenReference.From(jt);
}
return obj;
}
public object GetDeserializedobject(object obj,Type targettype)
{
if (obj == null)
{
return null;
}
else if (obj is JTokenReference reference &&
typeof(JToken).IsAssignableFrom(targettype))
{
return reference.To();
}
return obj;
}
}
[DataContract(Name = "Test",Namespace = "urn:actors")]
[Serializable]
public class Test
{
[DataMember(Name = "obj",Isrequired = false)]
public JObject obj;
}
static void Main(string[] args)
{
var serializer = new DataContractSerializer(typeof(Test),new DataContractSerializerSettings()
{
MaxItemsInObjectGraph = int.MaxValue,KNownTypes = new Type[] { typeof(JTokenReference),typeof(JObject),typeof(JToken) },});
serializer.SetSerializationSurrogateProvider(ActorDataContractSurrogate.Instance);
MemoryStream stream1 = new MemoryStream();
var writer = XmlDictionaryWriter.CreateBinaryWriter(stream1);
var obj = new JObject();
var test = new test()
{
obj = obj,};
serializer.WriteObject(writer,test);
writer.Flush();
Console.WriteLine(System.Text.Encoding.UTF8.GetString(stream1.GetBuffer(),checked((int)stream1.Length)));
}
}
我正在尝试定义一个新类型 JTokenReference 以在序列化时替换 JObject/JToken,但它在替换发生之前崩溃了。似乎无法解析类型。
解决方法
TL;DR
您的方法是合理的,应该可行,但由于 ISerializationSurrogateProvider
功能中的递归集合类型似乎存在错误而失败。每当您需要序列化 JToken
时,您都需要更改设计以使用代理属性,例如如下:
[IgnoreDataMember]
public JObject obj { get; set; }
[DataMember(Name = "obj",Order = 0,IsRequired = false)]
string objSurrogate { get { return obj?.ToString(Newtonsoft.Json.Formatting.None); } set { obj = (value == null ? null : JObject.Parse(value)); } }
说明
您遇到的崩溃是堆栈溢出,可以更简单地重现如下。当数据协定序列化程序编写一个泛型如 List<string>
时,它通过组合泛型类和参数名称来构造一个 data contract name,如下所示:
-
List<string>
:ArrayOfstring
-
List<List<string>
:ArrayOfArrayOfstring
-
List<List<List<string>>>
:ArrayOfArrayOfArrayOfstring
等等。随着通用嵌套变得更深,名称变得更长。那么,如果我们定义一个像下面这样的自递归集合类型会发生什么?
public class RecursiveList<T> : List<RecursiveList<T>>
{
}
好吧,如果我们尝试使用数据契约序列化程序序列化这些列表之一,它会因堆栈溢出异常而崩溃,试图找出契约名称。 Demo fiddle #1 here -- 您需要取消注释行 //Test(new RecursiveList<string>());
才能看到崩溃:
Stack overflow.
at System.ModuleHandle.ResolveType(System.Runtime.CompilerServices.QCallModule,Int32,IntPtr*,System.Runtime.CompilerServices.ObjectHandleOnStack)
at System.ModuleHandle.ResolveTypeHandleInternal(System.Reflection.RuntimeModule,System.RuntimeTypeHandle[],System.RuntimeTypeHandle[])
at System.Reflection.RuntimeModule.ResolveType(Int32,System.Type[],System.Type[])
at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(System.Reflection.MetadataToken,System.Reflection.MetadataImport ByRef,System.Reflection.RuntimeModule,System.Reflection.MetadataToken,System.RuntimeType,Boolean,ListBuilder`1<System.Object> ByRef,System.RuntimeType ByRef,System.IRuntimeMethodInfo ByRef,Boolean ByRef)
at System.Reflection.CustomAttribute.IsCustomAttributeDefined(System.Reflection.RuntimeModule,Boolean)
at System.Reflection.CustomAttribute.IsDefined(System.RuntimeType,Boolean)
at System.Runtime.Serialization.CollectionDataContract.IsCollectionOrTryCreate(System.Type,System.Runtime.Serialization.DataContract ByRef,System.Type ByRef,Boolean)
at System.Runtime.Serialization.CollectionDataContract.IsCollectionHelper(System.Type,Boolean)
at System.Runtime.Serialization.DataContract.GetNonDCTypeStableName(System.Type)
at System.Runtime.Serialization.DataContract.GetStableName(System.Type,Boolean ByRef)
at System.Runtime.Serialization.DataContract.GetCollectionStableName(System.Type,System.Type,System.Runtime.Serialization.CollectionDataContractAttribute ByRef)
at System.Runtime.Serialization.DataContract.GetNonDCTypeStableName(System.Type)
at System.Runtime.Serialization.DataContract.GetStableName(System.Type,Boolean ByRef)
糟糕。好吧,如果我们为 RecursiveList<string>
public class RecursiveListStringSurrogate
{
// A dummy surrogate that serializes nothing,for testing purposes.
}
public class RecursiveListStringSurrogateSelector : ISerializationSurrogateProvider
{
public object GetDeserializedObject(object obj,Type targetType)
{
if (obj is RecursiveListStringSurrogate)
return new RecursiveList<string>();
return obj;
}
public object GetObjectToSerialize(object obj,Type targetType)
{
if (obj is RecursiveList<string>)
return new RecursiveListStringSurrogate();
return obj;
}
public Type GetSurrogateType(Type type)
{
if (type == typeof(RecursiveList<string>))
return typeof(RecursiveListStringSurrogate);
return type;
}
}
使用该代理,确实可以成功序列化一个空的 new RecursiveList<string>()
,如
<RecursiveListStringSurrogate xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/" />
演示小提琴 #2 here。
好的,现在让我们尝试在 RecursiveList<string>
嵌入模型中时使用代理,例如:
public class Model
{
public RecursiveList<string> List { get; set; }
}
好吧,当我尝试使用空列表序列化此模型的实例时,崩溃又回来了。演示小提琴 #3 here - 您需要取消注释行 //Test(new Model { List = new RecursiveList<string>() });
才能看到崩溃。
再次糟糕。不完全清楚为什么这会失败。我只能推测,在某处,Microsoft 保留了一个字典,将原始数据合同名称映射到代理数据合同名称——这会导致堆栈溢出,只需生成一个字典键即可。
现在这与 JObject
和您的 Test
类有什么关系?事实证明,JObject
是递归集合类型的另一个示例。它实现了 IDictionary<string,JToken?>
和 JToken
依次实现了 IEnumerable<JToken>
,从而触发了我们在包含 RecursiveList<string>
的简单模型中看到的相同堆栈溢出。
您甚至可能想向 Microsoft report an issue 说明这一点(尽管我不知道他们是否正在修复数据协定序列化程序的错误。)
解决方法
为避免此问题,您需要修改模型以使用 JToken
成员的代理属性,如本答案开头所示:
[DataContract(Name = "Test",Namespace = "urn:actors")]
public class Test
{
[IgnoreDataMember]
public JObject obj { get; set; }
[DataMember(Name = "obj",IsRequired = false)]
string objSurrogate { get { return obj?.ToString(Newtonsoft.Json.Formatting.None); } set { obj = (value == null ? null : JObject.Parse(value)); } }
}
可以序列化成功的如下:
var obj = new JObject();
var test = new Test()
{
obj = obj,};
var serializer = new DataContractSerializer(test.GetType());
MemoryStream stream1 = new MemoryStream();
var writer = XmlDictionaryWriter.CreateBinaryWriter(stream1);
serializer.WriteObject(writer,test);
writer.Flush();
Console.WriteLine(System.Text.Encoding.UTF8.GetString(stream1.GetBuffer(),checked((int)stream1.Length)));
注意事项:
-
如果您需要将
JToken
序列化为 根对象,您可以将其包装在某个容器对象中,或者使用您问题中的ActorDataContractSurrogate
。正如我们所见,序列化功能似乎确实适用于递归集合类型,因为它们是根对象。 -
由于您要序列化为二进制文件,为了提高效率,我建议将
JObject
格式化为Formatting.None
。 -
代理属性可以是私有的,只要用
[DataMember]
标记即可。
演示小提琴 #4 here。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。