3

我正在使用网络服务来获取有关路线里程的数据。然后我使用反序列化器来解析它。这是 JSON 的外观:

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

有了这个回应,我遇到了几个问题。为什么被包装到集合中,如何设置对象模型?它还抱怨特殊的 __type 属性。所以,我做了“hack”和“prepped”字符串:

// 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我可以做更多的黑客攻击并将那些字符串替换为内部有2个对象的对象,但我想知道是否有“正确”的方式来处理它。

4

2 回答 2

5

"__type"参数加上DataContractJsonSerializer表示多态类型信息。从文档

多态性

多态序列化包括序列化派生类型的能力,其中它的基类型是预期的。WCF 支持 JSON 序列化,类似于支持 XML 序列化的方式。例如,您可以在期望 MyBaseType 的地方序列化 MyDerivedType,或者在期望 Object 的地方序列化 Int ...

保留类型信息

如前所述,JSON 支持多态性,但有一些限制......

为了保留类型标识,在将复杂类型序列化为 JSON 时,可以添加“类型提示”,并且反序列化器会识别该提示并采取适当的行动。“类型提示”是一个 JSON 键/值对,键名为“__type”(两个下划线后跟单词“type”)。该值是“DataContractName:DataContractNamespace”形式的 JSON 字符串(直到第一个冒号都是名称)。

为了使用这种机制来(反)序列化多态类型,所有可能的派生类型必须在DataContractJsonSerializer. 有关如何执行此操作的讨论,请参阅数据协定已知类型。

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

手动解决方案

您的问题的一种可能解决方案是手动创建与数据联系人层次结构相对应的 ac# 类层次结构,并使用DataContractDataMember属性正确注释。然后,您可以利用数据协定序列化程序的“类型提示”功能,在反序列化期间自动创建正确的子类。感谢 google,您看到的类看起来记录在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 List<Coordinates> GeoTunnelPoints { get; set; }
}

注意[KnownType(typeof(XXXReport))]附加到的属性Report。为了正确反序列化 JSON,所有预期的子类Report必须显示为已知类型。 根据文档,有 11 个可能的子类,因此您需要为您可能从 Web 服务收到的所有子类提供类。

现在您可以反序列化您的rawJSONas a 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 服务看起来相当复杂。手动重新创建它的所有类会很烦人。

自动解决方案

由于您的 Web 服务看起来是 WCF 服务,因此希望他们已经发布了它的Service Metadata。如果有,它将允许您使用Visual Studio 中的添加服务引用自动生成客户端。有关如何执行此操作的说明,请参阅如何:创建 Windows Communication Foundation 客户端如何:添加、更新或删除服务引用

再次由 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

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

创建客户端后,您可以像调用本地 c# API 一样访问 Web 服务。请参阅使用 WCF 客户端访问服务以了解如何操作。创建和使用您的第一个 WCF 服务一文概述了整个过程。

于 2015-12-23T12:22:00.717 回答
1

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

这是一个,它通过以下方式解决:

将 WebMethod 返回类型更改为对象,即

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

代替

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

另一个解决了

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

我不确定如何编写对象来正确解析它

根据响应,您可以构建适合您的 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);
于 2015-12-23T06:52:33.227 回答