我计划构建一个返回序列化为 JSON 的通用字典对象的 WCFservice。不幸的是,序列化失败了,因为对象可能总是不同的。KnownTypes 无能为力,因为属性类型是 Dictionary,我不能说 KnownType,因为类可能总是不同的。
如果可以序列化“未知类型”,有什么想法吗?
我不介意为我的每个类指定 DataContract/DataMember,但是(至少对于原型版本)我不希望每个响应都有强类型。Javascript 客户端根本不在乎。
匿名课程怎么样?
我计划构建一个返回序列化为 JSON 的通用字典对象的 WCFservice。不幸的是,序列化失败了,因为对象可能总是不同的。KnownTypes 无能为力,因为属性类型是 Dictionary,我不能说 KnownType,因为类可能总是不同的。
如果可以序列化“未知类型”,有什么想法吗?
我不介意为我的每个类指定 DataContract/DataMember,但是(至少对于原型版本)我不希望每个响应都有强类型。Javascript 客户端根本不在乎。
匿名课程怎么样?
注意:我在回答的开头已经详细介绍了 JavaScriptSerializer,如果您只是想了解原始问题中提到的已知类型问题的解决方案,请跳到答案的末尾。
表现
根据我运行的基准测试,JavaScriptSerializer 比其他替代方案慢得多,并且与 DataContractSerializer 相比,序列化/反序列化对象所需的时间是 DataContractSerializer 的 2 倍。
不需要已知类型
也就是说,JavascriptSerializer 更灵活,因为它不需要您提前指定“已知类型”,并且至少在字典的情况下,序列化的 JSON 更清晰(请参见此处的示例)。
围绕已知类型的灵活性的另一面是它无法将相同的 JSON 字符串反序列化回原始类型。例如,假设我有一个简单的Person
类:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
如果我在序列化之前创建一个类的实例Dictinoary<string, object>
并向其添加一个实例:Person
var dictionary = new Dictionary<string, object>();
dictionary.Add("me", new Person { Name = "Yan", Age = 30 });
var serializer = new new JavaScriptSerializer();
var json = serializer .Serialize(dictionary);
我将得到以下 JSON {"me":{"Name":"Yan","Age":30}}
,它非常干净但没有任何类型信息。因此,假设您有两个具有相同成员定义的类,或者如果Person
是子类而不引入任何其他成员:
public class Employee : Person
{
}
那么序列化程序根本无法保证可以将 JSON{"Name":"Yan","Age":30}
反序列化为正确的类型。
如果您{"me":{"Name":"Yan","Age":30}}
使用 JavaScriptSerializer 进行反序列化,则在字典中您会得到与“me”关联的值不是一个实例,Person
而是Dictionary<string, object>
一个简单的属性包。
如果你想取回一个Person
实例,你可以(虽然你很可能永远不会想要!)Dictionary<string, object>
使用ConvertToType
辅助方法来转换它:
var clone = serializer.Deserialize<Dictionary<string, object>>(json);
var personClone = serializer.ConverToType<Person>(clone["me"]);
另一方面,如果您不需要担心将这些 JSON 反序列化为正确的类型,并且 JSON 序列化不是性能瓶颈(分析您的代码并找出在序列化上花费了多少 CPU 时间(如果您还没有这样做的话)已经)然后我会说只使用 JavaScriptSerializer。
注入已知类型
如果,归根结底,您仍然需要使用 DataContractSerializer 并需要注入那些 KnownTypes,您可以尝试以下两件事。
1) 将已知类型的数组传递给 DataContractSerializer构造函数。
2) 将DataContractResolver的子类(具有定位您感兴趣的类型的方法)传递给 DataContractSerializer构造函数
您可以创建一个“已知类型注册表”来跟踪可以添加到字典中的类型,如果您控制需要注入 DataContractSerializer 的所有类型,您可以尝试最简单的方法:
使用静态方法创建一个KnownTypeRegister
类以将类型添加到已知类型列表中:
公共静态类 KnownTypeRegister { 私有静态只读 ConcurrentBag _knownTypes = new ConcurrentBag(); 公共静态无效添加(类型类型) { _knownTypes.Add(type); } 公共静态 IEnumerable Get() { 返回 _knownTypes.ToArray(); } }
添加一个使用寄存器注册类型的静态构造函数:
[数据合约] 公共类人 { 静态人() { KnownTypeRegister.Add(typeof(Person)); } [数据成员] 公共字符串名称 { 获取;放; } [数据成员] 公共 int 年龄 { 得到;放; } }
构造序列化器时从寄存器中获取已知类型的数组:
var serializer = new DataContractSerializer(typeof(Dictionary<string, object>), KnownTypeRegister.Get());
更多动态/更好的选项是可能的,但它们也更难以实现,如果您想阅读更多关于动态已知类型解析的信息,请查看 Juval Lowy 的 MSDN 文章关于该主题的此处。此外,Carlos Figueira 的这篇博文还详细介绍了更高级的技术,例如动态生成类型,非常值得一读!
免责声明我不建议您object
在 WCF 端点上公开;尽管这看起来“灵活”,但这不是一个好主意,因为您没有指定您的服务将提供哪种信息。
现在寻求答案
如果您的 WCF 调用正在被 ajax 调用消耗,并且正如您所说,Javascript client just doesn't care.
那么为什么不让您的 WCF 调用简单地返回一个字符串呢?然后你的 WCF 调用的内部可以Dictionary<string, object>
使用 JavaScriptSerializer序列化
public string MyServiceMethod()
{
var hash = new Dictionary<string, object>();
hash.Add("key1", "val1");
hash.Add("key2", new { Field1 = "field1", Field2 = 42});
var serialized = new JavaScriptSerializer().Serialize(hash);
return serialized;
}
免责声明2 这是达到目的的一种手段(因为您提出了这个问题) - 对于生产质量应用程序,我将有一个定义明确的接口,因此很清楚所请求的内容并通过网络发送回。