我正在使用网络服务获取有关路线里程的数据.然后我使用反序列化器来解析它.以下是JSON的外观:
[{"__type":"CalculateMilesReport:http:\/\/pcmiler.alk.com\/APIs\/v1.0","RouteID":null,"TMiles":445.5]
有了这个回应,我有几个问题.为什么包装成集合,如何设置对象模型?它还抱怨特殊的__type属性.所以,我做了"黑客"和"准备"字符串:
// Cut off first and last charachters [] - they send objects as arrays rawJSON = rawJSON.Substring(1, rawJSON.Length - 2); // Hide "__type" attribute as it messes up serializer with namespace rawJSON = rawJSON.Replace("__type", "type");
然后一切都使用这个对象:
[DataContract] public class PCMilerResponse { [DataMember(Name = "Errors", EmitDefaultValue = false)] public PCMilerError[] Errors { get; set; } [DataMember(Name = "TMiles", EmitDefaultValue = false)] public decimal DrivingDistance { get; set; } }
现在我修改了对Web服务的调用,我得到了以下响应
[ {"__type":"CalculateMilesReport:http:\/\/pcmiler.alk.com\/APIs\/v1.0","RouteID":null,"TMiles":445.5}, {"__type":"GeoTunnelReport:http:\/\/pcmiler.alk.com\/APIs\/v1.0","RouteID":null,"GeoTunnelPoints": [{"Lat":"34.730466","Lon":"-92.247147"},{"Lat":"34.704863","Lon":"-92.29329"},{"Lat":"34.676312","Lon":"-92.364654"},{"Lat":"29.664271","Lon":"-95.236735"}] } ]
现在有理由为什么有数组和"__type".但我不确定如何编写对象来正确解析它.我想需要应用特殊属性,可能需要通用数组吗?有关如何正确反序列化的任何帮助?
PS我可以做更多的黑客攻击并替换那些使其成为对象的字符串,但我不知道是否有"正确"的方法来处理它.
该"__type"
参数是由添加DataContractJsonSerializer
到代表多态型的信息.来自文档:
多态性
多态序列化包括序列化期望其基类型的派生类型的能力.WCF支持JSON序列化,与支持XML序列化的方式相当.例如,您可以序列化期望MyBaseType的MyDerivedType,或序列化Int期望Object的Int ...
保留类型信息
如前所述,JSON支持多态,但有一些限制......
为了保留类型标识,在将复杂类型序列化为JSON时,可以添加"类型提示",并且反序列化器可以识别提示并采取相应的操作."类型提示"是一个JSON键/值对,键名为"__type"(两个下划线后跟单词"type").该值是"DataContractName:DataContractNamespace"形式的JSON字符串(第一个冒号的所有内容都是名称).
为了使用此机制(de)序列化多态类型,必须预先指定所有可能的派生类型DataContractJsonSerializer
.有关如何执行此操作的讨论,请参阅数据合同已知类型.
因此,看起来您的Web服务正在返回多态类型的数组.怎么办呢?
手动解决方案
解决问题的一种可能方法是手动创建与数据联系人层次结构相对应的ac#类层次结构,DataContract
并使用和DataMember
属性进行正确注释.然后,您可以利用数据协定序列化程序的"类型提示"功能,以便在反序列化期间自动创建正确的子类.由谷歌提供,您看到的课程将在PC*MILER Web Services API:Report Class中记录.使用此文档,您的类应如下所示:
public static class Namespaces { public const string Pcmiler = @"http://pcmiler.alk.com/APIs/v1.0"; } [DataContract(Namespace = Namespaces.Pcmiler)] public class Coordinates { public double Lat { get; set; } public double Lon { get; set; } } [KnownType(typeof(CalculateMilesReport))] [KnownType(typeof(GeoTunnelReport))] [DataContract(Namespace = Namespaces.Pcmiler)] public abstract class Report { [DataMember] public string RouteID { get; set; } } [DataContract(Namespace = Namespaces.Pcmiler)] public class CalculateMilesReport : Report { [DataMember] public double TMiles { get; set; } } [DataContract(Namespace = Namespaces.Pcmiler)] public class GeoTunnelReport : Report { [DataMember] public ListGeoTunnelPoints { get; set; } }
请注意[KnownType(typeof(XXXReport))]
附加的属性Report
.为了正确反序列化JSON,所有预期的子类Report
必须显示为已知类型. 根据文档,有11个可能的子类,因此您需要为您可能从Web服务收到的所有子类提供类.
现在,你可以反序列化rawJSON
的List
,一切都在你的样品JSON应该正确地阅读,因为你已经正确匹配的数据协定名称,命名空间,并把Web服务的类型层次:
var list = DataContractJsonSerializerHelper.GetObject>(rawJSON);
运用
public static class DataContractJsonSerializerHelper { private static MemoryStream GenerateStreamFromString(string value) { return new MemoryStream(Encoding.Unicode.GetBytes(value ?? "")); } public static T GetObject(string json) { var serializer = new DataContractJsonSerializer(typeof(T)); using (var stream = GenerateStreamFromString(json)) { return (T)serializer.ReadObject(stream); } } }
但是,该Web服务看起来相当精细.手动重新创建它的所有类将是令人厌倦的.
自动解决方案
由于您的Web服务似乎是WCF服务,因此希望他们已发布其服务元数据.如果有,它将允许您使用Visual Studio中的" 添加服务引用" 自动生成客户端.有关如何执行此操作的说明,请参见如何:创建Windows Communication Foundation客户端以及如何:添加,更新或删除服务引用.
再次由谷歌提供,您的服务似乎确实提供了其元数据,请访问http://pcmiler.alk.com/APIs/REST/v1.0/service.svc?wsdl.干
svcutil.exe http://pcmiler.alk.com/APIs/REST/v1.0/service.svc?wsdl
似乎生成了一组似乎合理的客户端类,与上面创建的手动类一致.但是,您应该从Web服务中双重检查文档,以确保使用正确的方式来使用其服务元数据.
创建客户端后,您可以像访问本地c#API一样访问Web服务.有关方法,请参阅使用WCF客户端访问服务.文章创建和使用您的第一个WCF服务给出整个过程的概述.