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

How to deserialize JSON with unnamed collection of types using DataContractSerializer

我正在使用网络服务获取有关路线里程的数据。然后我使用解串器来解析它。这是 JSON 的样子:

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

有了这个回复,我遇到了几个问题。为什么被包装到集合中以及如何设置对象模型?它还在抱怨特殊的 __type 属性。所以,我做了 "hack" 和 "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");

然后一切都适用于这个对象:

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

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

现在我修改了对网络服务的调用,我得到了以下响应

[
{"__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”了。但我不确定如何编写对象来正确解析它。我想需要应用特殊属性,也许需要通用数组?关于如何正确反序列化它的任何帮助?

P.S。我可以做更多的黑客攻击并替换那些使其成为对象的字符串,其中包含 2 个对象,但我想知道是否有 "proper" 方法来处理它。

关于使 __type 属性消失,有关于 SO 的讨论。

Here is one,解决方法如下:

将 WebMethod return 类型更改为对象,即

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

而不是

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

Another one

解决

将命名空间参数 [DataContract(Namespace = "")] 添加到数据协定。

I'm not sure how to write object to properly parse it

根据您可以构建 类 的响应,您的 JSON 将适合其中,但由于您拥有模型 类,您应该使用相同的模型您的 JSON 已建成。也许我没有从你的问题中得到正确的东西。

这是一个精心制作的模型示例,您的 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;
    }
}

用法示例:

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

"__type"参数由DataContractJsonSerializer添加,表示多态类型信息。来自 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. For example, you can serialize MyDerivedType where MyBaseType is expected, or serialize Int where Object is expected...

Preserving Type Information

As stated earlier, polymorphism is supported in JSON with some limitations...

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. The "type hint" is a JSON key/value pair with the key name of "__type" (two underscores followed by the word "type"). The value is a JSON string of the form "DataContractName:DataContractNamespace" (anything up to the first colon is the name).

为了使用此机制来(反)序列化多态类型,必须预先指定所有可能的派生类型 DataContractJsonSerializer。有关如何执行此操作的讨论,请参阅 Data Contract Known Types

因此,看起来您的 Web 服务正在返回一个多态类型数组。如何处理?

手动解决方案

您的问题的一种可能解决方案是手动创建与数据联系层次结构相对应的 c# class 层次结构,并使用 DataContractDataMember 属性进行适当注释。然后,您可以利用数据协定序列化程序的 "type hint" 功能在反序列化期间自动创建正确的子 class。由 google 提供,您看到的 classes 看起来已记录在 PC*MILER Web Services API: Report Class 中。使用此文档,您的 classes 应如下所示:

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; }
}

请注意 [KnownType(typeof(XXXReport))] attributes attached to Report. In order to deserialize the JSON correctly, all expected subclasses of Report must appear as known types. According to the documentation 有 11 个可能的子classes,因此您需要为所有可能从您的网络服务收到的子class提供 classes。

现在您可以将 rawJSON 反序列化为 List<Report>,样本 JSON 中的所有内容都应该正确读取,因为您已正确匹配数据协定名称、命名空间、并键入 Web 服务的层次结构:

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

使用

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);
        }
    }
}

但是,该 Web 服务 looks rather elaborate。手动重新创建其所有 classes 会很烦人。

自动解决方案

既然您的 Web 服务似乎是 WCF 服务,希望他们已经发布了它 Service Metadata. If they have, it will allow you to generate a client automatically using Add Service Reference in 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

再次感谢 google,您的服务 确实 http://pcmiler.alk.com/APIs/REST/v1.0/service.svc?wsdl 提供了它的元数据。做

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

似乎生成了一组合理的客户端 classes,与上面创建的手册 classes 一致。但是,您应该仔细检查 Web 服务的文档,以确保这是使用其服务元数据的正确方法。

创建客户端后,您可以像调用本地 c# 一样访问 Web 服务 API。请参阅 Accessing Services Using a WCF Client for how. The article Creating and Consuming Your First WCF Service 概述整个过程。