将XML字符串进行通用反序列化

17

我有一堆不同的DTO类。它们在某个时刻被序列化为XML字符串,然后发送到Web应用程序的客户端。现在,当客户端回传一个XML字符串时,我需要将其反序列化回代表它的DTO类实例。问题是我想让它通用,并且可能是一个函数,该函数接收一个XML字符串并输出一个类型的对象。大致上像这样:

public sometype? Deserialize (string xml)
{
//some code here
return objectFromXml;
}

编辑:糟糕的例子!我刚刚自相矛盾了!

我无法执行以下操作:

Person person = Deserialize(personXmlStringFromClient);

因为我不知道personXmlStringFromClient是Person DTO对象实例的表示。

我不知道给我的序列化对象是什么,这似乎是我的问题所在。我一直在阅读关于反射和其他技术的内容,这些技术涉及将类型插入到xml中,以便反序列化器知道如何处理它。我无法将所有这些内容整合成一个可工作的部分。而且,在几乎大多数示例中,作者都知道反序列化后会有什么类型。欢迎任何建议!如果我需要对序列化过程进行特殊处理,请也分享一下。

5个回答

27

您可以使用通用的:

    public T Deserialize<T>(string input)
        where T : class
    {
        System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(T));

        using (StringReader sr = new StringReader(input))
            return (T)ser.Deserialize(sr);
    }

如果你不知道它将是哪种类型,我假设你有一定数量的可能类型,并且您可以尝试对每个类型进行反序列化,直到您遇到异常为止。这并不是很好,但它可以工作。
或者,您可以检查 XML 的开头以获取外部对象名称,并希望能够从那里确定类型。这将根据 XML 的外观而异。
编辑:根据您的编辑,如果调用方知道他们正在传递的类型,他们是否可以作为服务的附加参数提供完全限定的类型名称字符串?
如果是这样,您可以这样做:
    Type t = Type.GetType(typeName);

并将Deserialize方法更改为以下内容:

public object Deserialize(string input, Type toType)
{
    System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(toType);

    using (StringReader sr = new StringReader(input))
        return ser.Deserialize(sr);
}

然而,仅仅这样只能得到一个对象... 如果所有相关的类型都实现了一个公共接口,你可以像上面一样进行反序列化,但是将返回类型更改为接口(并在返回语句中进行强制转换)。


抱歉!我自己都搞混了。我不能使用你的函数,因为我不知道T是什么。 - Dimskiy
@Dimskiy:你知道T 可能 代表什么(即,一系列潜在的类型)吗? - Mark Avenius
如果我可以将类型作为字符串获取,那么如何将字符串“Person”转换为实际类型? - Dimskiy
我目前有大约50-60个DTO。我不知道哪一个作为XML字符串输入。我可以将其类型作为字符串提取出来。 - Dimskiy
@Dimskiy:所有可能的类型都可以强制转换以实现一个公共接口吗? - Mark Avenius
1
我发现自己在想为什么这个常见且简单的函数没有内置于.NET中。不得不从两个其他类构建它似乎很笨拙。 - O'Rooney

2
如果您不介意使用泛型:
public static T DeserializeFromString<T>(string value)
{
    T outObject;
    XmlSerializer deserializer = new XmlSerializer(typeof(T));
    StringReader stringReader = new StringReader(value);
    outObject = (T)deserializer.Deserialize(stringReader);
    stringReader.Close();
    return outObject;
}

编辑:如果您不知道XML将转换为哪种类型的对象,则无法返回除对象以外的任何内容。当然,您可以在测试后了解您刚刚获得的对象是什么类型。一种可能的方法是将可能反序列化的所有对象类型传递给XmlSerializer
public static object DeserializeFromString(string value, Type[] types)
{
    XmlSerializer deserializer = new XmlSerializer(typeof(object), types);
    StringReader stringReader = new StringReader(value);
    object outObject = deserializer.Deserialize(stringReader);
    stringReader.Close();
    return outObject;
}

使用这种方法将假设您的对象已经被装箱(您应该以相同的方式进行序列化),这将给您提供以下类似XML的内容:
<object xsi:type="Person">
    ...
</object>

如果您有多种类型需要传递,可以使用反射来获取它们,例如使用类似 Assembly.GetExecutingAssembly().GetTypes() 的方法。请注意保留 HTML 标签。

1
忘记泛型吧。如果你不知道返回类型怎么办?如果你使用的是Visual C# 2010或更高版本,这就是新的dynamic关键字的美妙之处。下面是我编写的一个示例序列化器类和样本用法,它只需要XML字符串并尝试解析为通用的System类型,在成功时返回输出值。你可能可以将其扩展到其他自定义类型,但它们可能需要具有默认构造函数,并且您可能需要进行更多的解析以获取类型名称,然后在程序集中获取它的路径。它有点棘手,但一旦你掌握了下面代码的工作原理,就会开始打开一堆可能性。这难道不是你正在寻找的吗?你的问题是如何在不知道该类型的情况下获取该类型的对象(请注意,您仍然必须在代码中拥有该类型的定义才能对其进行反序列化)。让我解释一下。如果你看一下下面代码中如何使用assemblyFormatter,你会发现对于自己定义的类型(例如struct或enum)来说会更棘手,因为对于这些类型,你必须将assemblyFormatter作为myObject.GetType().FullName传递进去。这是激活器用于调用您的类型的默认构造函数以创建它以便能够创建一个序列化程序的字符串;它基本上归结为必须在程序集中知道该类型定义才能对其进行反序列化的复杂性。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace DynamicSerializer
{
    class Program
    {
        static void Main(string[] args)
        {
            bool myObject = true;
            // There are a bunch of other examples you can try out:
            // string myObject = "Hello, world.";
            // long myObject = 1000;
            // int myObject = 100;
            string mySerializedObject;
            if (Serializer.TrySerialize(myObject, out mySerializedObject))
            {
                Console.WriteLine("Serialized {0} as {1}.", myObject, mySerializedObject);
                dynamic myDeserializedObject;
                if (Serializer.TryDeserialize(mySerializedObject, out myDeserializedObject))
                {
                    Console.WriteLine("Deserialized {0} as {1}.", mySerializedObject, myDeserializedObject);
                }
            }
            Console.ReadLine();
        }

        class Serializer
        {
            public static bool TrySerialize(dynamic unserializedObject, out string serializedObject)
            {
                try
                {
                    StringWriter writer = new StringWriter();
                    XmlSerializer serializer = new XmlSerializer(unserializedObject.GetType());
                    serializer.Serialize(writer, unserializedObject);
                    serializedObject = writer.ToString();
                    return true;
                }
                catch
                {
                    serializedObject = null;
                    return false;
                }
            }

            // The assemblyFormatter parameter is normally not passed in. However, it may be passed in for cases where the type is a special case (such as for enumerables or structs) that needs to be passed into the serializer. If this is the case, this value should be passed in as yourObject.GetType().FullName.
            public static bool TryDeserialize(string serializedObject, out dynamic deserializedObjectOut, string assemblyFormatter = "System.{0}")
            {
                try
                {
                    StringReader reader = new StringReader(serializedObject);
                    XDocument document = XDocument.Load(reader);
                    string typeString = null;
                    // Map the object type to the System's default value types.
                    switch (document.Root.Name.LocalName)
                    {
                        case "string":
                            typeString = "String";
                            break;
                        case "dateTime":
                            typeString = "DateTime";
                            break;
                        case "int":
                            typeString = "Int32";
                            break;
                        case "unsignedInt":
                            typeString = "UInt32";
                            break;
                        case "long":
                            typeString = "Int64";
                            break;
                        case "unsignedLong":
                            typeString = "UInt64";
                            break;
                        case "boolean":
                            typeString = "Boolean";
                            break;
                        case "double":
                            typeString = "Double";
                            break;
                        case "float":
                            typeString = "Single";
                            break;
                        case "decimal":
                            typeString = "Decimal";
                            break;
                        case "char":
                            typeString = "Char";
                            break;
                        case "short":
                            typeString = "Int16";
                            break;
                        case "unsignedShort":
                            typeString = "UInt16";
                            break;
                        case "byte":
                            typeString = "SByte";
                            break;
                        case "unsignedByte":
                            typeString = "Byte";
                            break;
                    }
                    if (assemblyFormatter != "System.{0}")
                    {
                        typeString = document.Root.Name.LocalName;
                    }
                    if (typeString == null)
                    {
                        // The dynamic object's type is not supported.
                        deserializedObjectOut = null;
                        return false;
                    }
                    if (typeString == "String")
                    {
                        // System.String does not specify a default constructor.
                        XmlSerializer serializer = new XmlSerializer(typeof(String));
                        reader = new StringReader(serializedObject);
                        deserializedObjectOut = serializer.Deserialize(reader);
                    }
                    else
                    {
                        object typeReference;
                        if (assemblyFormatter != "System.{0}")
                        {
                            typeReference = Activator.CreateInstance(Type.GetType(assemblyFormatter));
                        }
                        else
                        {
                            typeReference = Activator.CreateInstance(Type.GetType(String.Format(assemblyFormatter, typeString)));
                        }
                        XmlSerializer serializer = new XmlSerializer(typeReference.GetType());
                        reader = new StringReader(serializedObject);
                        deserializedObjectOut = serializer.Deserialize(reader);
                    }
                    return true;
                }
                catch
                {
                    deserializedObjectOut = null;
                    return false;
                }
            }
        }
    }
}

0
如果您针对每种类型都有自定义的序列化/反序列化例程,您可以使用类似以下代码的方法:
public T Deserialize <T>(string xml)
{
    if(typeof(T) == typeof(Person))
    {
        // deserialize and return Person instance
    }
    else if(typeof(T) == typeof(Address)
    {
        // deserialize and return Address instance
    }
    ...
    ...
    ...
}

你可以调用

Person p = Deserialize<Person>(personXmlStringFromClient);

我有大约50-60个DTO,未来可能会添加新的。 - Dimskiy

0
如何创建一个非泛型的“前门”函数,其目的是找出这个问题?大多数XML模式使用对象名称或合理的类似物作为对象的最外层标记。

我有大约60个DTO,未来可能会有更多的添加 :( - Dimskiy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接