简体   繁体   English

如何使用DataContractSerializer使用未命名的类型集合反序列化JSON

[英]How to deserialize JSON with unnamed collection of types using DataContractSerializer

I'm using web service to get data about route mileage. 我正在使用网络服务获取有关路线里程的数据。 Then I'm using deserializer to parse it out. 然后我使用反序列化器来解析它。 Here is how JSON looks: 以下是JSON的外观:

[{"__type":"CalculateMilesReport:http:\/\/pcmiler.alk.com\/APIs\/v1.0","RouteID":null,"TMiles":445.5]

With this response I had couple issues. 有了这个回应,我有几个问题。 Why is is wrapped into collection and how do I set object model? 为什么包装成集合,如何设置对象模型? Also it was complaining about special __type attribute. 它还抱怨特殊的__type属性。 So, I did "hack" and "prepped" string: 所以,我做了“黑客”和“准备”字符串:

// 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");

Then everything worked with this object: 然后一切都使用这个对象:

[DataContract]
public class PCMilerResponse
{
    [DataMember(Name = "Errors", EmitDefaultValue = false)]
    public PCMilerError[] Errors { get; set; }

    [DataMember(Name = "TMiles", EmitDefaultValue = false)]
    public decimal DrivingDistance { get; set; }    
}

Now I modified call to web service and I get following response 现在我修改了对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"}]
}
]

Now it makes sense why there is array and "__type". 现在有理由为什么有数组和“__type”。 But I'm not sure how to write object to properly parse it. 但我不确定如何编写对象来正确解析它。 I guess special attributes need to be applied and maybe generic array need to be there? 我想需要应用特殊属性,可能需要通用数组吗? Any help on how to properly deserialize it? 有关如何正确反序列化的任何帮助?

PS I can do more hacking and replace those strings making it object with 2 objects inside, but I wonder if there is "proper" way to handle it. PS我可以做更多的黑客攻击并替换那些使其成为对象的字符串,但我不知道是否有“正确”的方法来处理它。

The "__type" parameter is added by DataContractJsonSerializer to represent polymorphic type information. DataContractJsonSerializer添加"__type"参数以表示多态类型信息。 From the docs : 来自文档

Polymorphism 多态性

Polymorphic serialization consists of the ability to serialize a derived type where its base type is expected. 多态序列化包括序列化期望其基类型的派生类型的能力。 This is supported for JSON serialization by WCF comparable to the way XML serialization is supported. WCF支持JSON序列化,与支持XML序列化的方式相当。 For example, you can serialize MyDerivedType where MyBaseType is expected, or serialize Int where Object is expected... 例如,您可以序列化期望MyBaseType的MyDerivedType,或序列化Int期望Object的Int ...

Preserving Type Information 保留类型信息

As stated earlier, polymorphism is supported in JSON with some limitations... 如前所述,JSON支持多态,但有一些限制......

To preserve type identity, when serializing complex types to JSON a "type hint" can be added, and the deserializer recognizes the hint and acts appropriately. 要保留类型标识,在将复杂类型序列化为JSON时,可以添加“类型提示”,并且反序列化程序可以识别提示并采取相应的操作。 The "type hint" is a JSON key/value pair with the key name of "__type" (two underscores followed by the word "type"). “类型提示”是一个JSON键/值对,键名为“__type”(两个下划线后跟单词“type”)。 The value is a JSON string of the form "DataContractName:DataContractNamespace" (anything up to the first colon is the name). 该值是“DataContractName:DataContractNamespace”形式的JSON字符串(第一个冒号的所有内容都是名称)。

In order to use this mechanism to (de)serialize a polymorphic type, all possible derived types must be specified up front to DataContractJsonSerializer . 为了使用此机制(de)序列化多态类型,必须在DataContractJsonSerializer之前预先指定所有可能的派生类型。 See Data Contract Known Types for a discussion of how to do this. 有关如何执行此操作的讨论,请参阅数据合同已知类型

Thus, it looks like your web service is returning an array of polymorphic types. 因此,看起来您的Web服务正在返回多态类型的数组。 How to handle this? 怎么办呢?

The Manual Solution 手动解决方案

One possible solution to your problem is to manually create ac# class hierarchy corresponding to the data contact hierarchy, properly annotated with DataContract and DataMember attributes. 解决问题的一种可能方法是手动创建与数据联系人层次结构相对应的ac#类层次结构,并使用DataContractDataMember属性进行正确注释。 Then you can leverage the "type hint" functionality of the data contract serializers to cause the correct subclass to be created automatically during deserialization. 然后,您可以利用数据协定序列化程序的“类型提示”功能,以便在反序列化期间自动创建正确的子类。 Courtesy of google, the classes you are seeing look to be documented at PC*MILER Web Services API: Report Class . 由谷歌提供,您看到的课程将在PC * MILER Web Services API:Report Class中记录 Using this documentation, your classes should look like: 使用此文档,您的类应如下所示:

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 List<Coordinates> GeoTunnelPoints { get; set; }
}

Note the [KnownType(typeof(XXXReport))] attributes attached to Report . 请注意附加到Report[KnownType(typeof(XXXReport))]属性。 In order to deserialize the JSON correctly, all expected subclasses of Report must appear as known types. 为了正确反序列化JSON, Report所有预期子类必须显示为已知类型。 According to the documentation there are 11 possible subclasses, so you will need to provide classes for all of them that you might receive from your web service. 根据文档 ,有11个可能的子类,因此您需要为您可能从Web服务收到的所有子类提供类。

Now you can deserialize your rawJSON as a List<Report> , and everything in your sample JSON should read in correctly, because you have correctly matched the data contract names, namespaces, and type hierarchies to that of the web service: 现在,您可以将rawJSON反序列rawJSON List<Report> ,并且示例JSON中的所有内容都应该正确读取,因为您已正确地将数据协定名称,名称空间和类型层次结构与Web服务的名称相匹配:

        var list = DataContractJsonSerializerHelper.GetObject<List<Report>>(rawJSON);

using 运用

public static class DataContractJsonSerializerHelper
{
    private static MemoryStream GenerateStreamFromString(string value)
    {
        return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
    }

    public static T GetObject<T>(string json)
    {
        var serializer = new DataContractJsonSerializer(typeof(T));
        using (var stream = GenerateStreamFromString(json))
        {
            return (T)serializer.ReadObject(stream);
        }
    }
}

However, that web service looks rather elaborate . 但是,该Web服务看起来相当精细 Manually recreating all its classes would be tiresome. 手动重新创建它的所有类将是令人厌倦的。

The Automatic Solution 自动解决方案

Since it appears your web service is a WCF service, hopefully they have published its Service Metadata . 由于您的Web服务似乎是WCF服务,因此希望他们已发布其服务元数据 If they have, it will allow you to generate a client automatically using Add Service Reference in Visual Studio. 如果有,它将允许您使用Visual Studio中的“ 添加服务引用” 自动生成客户端。 For instructions on how to do this, see How to: Create a Windows Communication Foundation Client and How to: Add, Update, or Remove a Service Reference . 有关如何执行此操作的说明,请参见如何:创建Windows Communication Foundation客户端以及如何:添加,更新或删除服务引用

Again courtesy of google, it appears your service does provide its metadata, at http://pcmiler.alk.com/APIs/REST/v1.0/service.svc?wsdl . 再次由谷歌提供,您的服务似乎确实提供了其元数据,请访问http://pcmiler.alk.com/APIs/REST/v1.0/service.svc?wsdl Doing

 svcutil.exe http://pcmiler.alk.com/APIs/REST/v1.0/service.svc?wsdl

Seems to generate a plausible set of client classes consistent with the manual classes created above. 似乎生成了一组似乎合理的客户端类,与上面创建的手动类一致。 However, you should doublecheck the documentation from your web service to ensure this the correct way to consume their service metadata. 但是,您应该从Web服务中双重检查文档,以确保使用正确的方式来使用其服务元数据。

Once a client has been created, you can access the web service as if you were calling a local c# API. 创建客户端后,您可以像访问本地c#API一样访问Web服务。 See Accessing Services Using a WCF Client for how. 有关方法,请参阅使用WCF客户端访问服务 The article Creating and Consuming Your First WCF Service gives an overview of the entire process. 创建和使用您的第一个WCF服务一文概述了整个过程。

About making the __type attribute disappear there are discussions on SO. 关于使__type属性消失,有关于SO的讨论。

Here is one , which solved in the following way: 这是一个 ,它通过以下方式解决:

change the WebMethod return type to object, ie 将WebMethod返回类型更改为object,即

[WebMethod]
public static object ApplyCredits(int addonid, int[] vehicleIds) 

instead of 代替

[WebMethod]
public static WebMethodReturn ApplyCredits(int addonid, int[] veh

Another one solved by 另一个解决了

Adding the namespace parameter [DataContract(Namespace = "")] to the data contract. 将namespace参数[DataContract(Namespace = "")]到数据协定中。

I'm not sure how to write object to properly parse it 我不确定如何编写对象来正确解析它

Based on the response you can build classes in which your JSON is going to fit, but since you have the model classes you are supposed to use the same from which your JSON was built. 根据响应,您可以构建JSON适合的类,但由于您拥有模型类,因此您应该使用与构建JSON相同的类。 Maybe I didn't get here something correctly from your question. 也许我没有从你的问题中得到正确的东西。

Here is a crafted model example in which your JSON would fit in: 这是一个精心设计的模型示例,其中您的JSON适合:

public class ResultType
{
    public string RouteID { get; set; }
    public List<GeoTunnelPoints> Points { get; set; }
    public double TMiles { get; set; }

    public ResultType()
    {
        RouteID = "";
        Points = new List<GeoTunnelPoints>();
        TMiles = 0;
    }
}

public class GeoTunnelPoints
{
    double Lat { get; set; }
    double Lon { get; set; }

    public GeoTunnelPoints()
    {
        Lat = 0.0;
        Lon = 0.0;
    }
}

Example usage: 用法示例:

// Your example JSON after excluding the __type
string input = 
                "[" +
                    "{" + 
                       "\"RouteID\":null, " +
                        "\"TMiles\":445.5}," +
                    "{" +
                        "\"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\"}" +
                              "]" +
                    "} " +
                "]";

List<ResultType> resultList = new List<ResultType>();
// This will be your C# result collection
resultList = new JavaScriptSerializer().Deserialize<List<ResultType>>(input);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM