c# - 如何使用 DataContractSerializer 反序列化具有未命名类型集合的 JSON

标签 c# .net json datacontractserializer datacontractjsonserializer

我正在使用网络服务获取有关路线里程的数据。然后我使用解串器来解析它。这是 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; }    
}

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

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

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

最佳答案

"__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# 类层次结构,并使用 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 List<Coordinates> GeoTunnelPoints { get; set; }
}

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

现在您可以反序列化您的 rawJSON作为List<Report> ,并且示例 JSON 中的所有内容都应该正确读取,因为您已经将数据协定名称、 namespace 和类型层次结构与 Web 服务的名称、 namespace 和类型层次结构正确匹配:

        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 .手动重新创建它的所有类会很烦人。

自动解决方案

既然您的 Web 服务看起来是 WCF 服务,希望他们已经发布了它的 Service Metadata .如果有,它将允许您使用 Visual Studio 中的添加服务引用自动生成客户端。有关如何执行此操作的说明,请参阅 How to: Create a Windows Communication Foundation ClientHow to: Add, Update, or Remove a Service Reference .

同样由谷歌提供,看来您的服务确实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 服务。参见 Accessing Services Using a WCF Client如何。文章Creating and Consuming Your First WCF Service给出了整个过程的概述。

关于c# - 如何使用 DataContractSerializer 反序列化具有未命名类型集合的 JSON,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34384838/

相关文章:

.net - Windows 服务中定时器的使用

c# - 尝试进行串行端口通信,返回 0x102 作为返回码

c# - 多少数据对于 XML 文件来说太多了?有哪些基于文件的数据库替代方案?

c# - 在 JetBrains PyCharm 上使用 IronPython

.net - 开发 MS Word 插件

.net - 不包含多个特定单词的字符串的正则表达式

javascript - 将 SSID 列表转换为 JSON/Array

json - React JSON 架构依赖关系

java - 如何将 JSON (org.jooq.JSON) 转换为 Java 对象 (POJO)

c# - 检查是否启用了 UserPrincipal