.net xml 字符串的通用反序列化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4996876/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic deserialization of an xml string
提问by Dimskiy
I have a bunch of different DTO classes. They are being serialized into an XML string at one point and shot over to client-side of the web app. Now when the client shoots back an XML string, I need to deserialize it back to an instance of the DTO class that it represents. The problem is that I want to make it generic and possibly a function which takes in an xml string and spits out an object of a type. Something like a long these lines:
我有一堆不同的 DTO 类。它们一次被序列化为一个 XML 字符串,然后发送到 Web 应用程序的客户端。现在,当客户端返回一个 XML 字符串时,我需要将它反序列化回它所代表的 DTO 类的一个实例。问题是我想让它通用,并且可能是一个函数,它接受一个 xml 字符串并吐出一个类型的对象。像长这些行的东西:
public sometype? Deserialize (string xml)
{
//some code here
return objectFromXml;
}
EDIT: Horrible example! I just contradicted myself!
编辑:可怕的例子!我只是自相矛盾!
I cannot do the following:
我不能做以下事情:
Person person = Deserialize(personXmlStringFromClient);
because I don't know that personXmlStringFromClient is a representation of Person DTO object instance.
因为我不知道 personXmlStringFromClient 是 Person DTO 对象实例的表示。
I don't know what serialized object is given to me and that seems to be my problem here. I've been reading about reflection and other techniques which involve sticking the type into the xml so that deserializer knows what to do with it. I can't seem to pull it all together into one working piece. Also, in pretty much most examples, the author knows what type there will be after deserialization. Any suggestion is welcome! If I need to do something special with the serialization process, please share that too.
我不知道给了我什么序列化对象,这似乎是我的问题。我一直在阅读关于反射和其他技术,这些技术涉及将类型粘贴到 xml 中,以便反序列化器知道如何处理它。我似乎无法将它们全部整合到一个工件中。此外,在大多数示例中,作者知道反序列化后会是什么类型。欢迎任何建议!如果我需要对序列化过程做一些特别的事情,请也分享一下。
回答by Mark Avenius
You can use a generic:
您可以使用泛型:
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);
}
If you don't know which type it will be, I assume you have a fixed number of possible types, and you could try deserializing to each one until you don't encounter an exception. Not great, but it would work.
如果您不知道它将是哪种类型,我假设您有固定数量的可能类型,并且您可以尝试对每个类型进行反序列化,直到您没有遇到异常。不是很好,但它会起作用。
Or, you could inspect the start of the xml for the outer object name and hopefully be able to determine the type from there. This would vary depending on what the xml looks like.
或者,您可以检查外部对象名称的 xml 的开头,并希望能够从那里确定类型。这将根据 xml 的外观而有所不同。
Edit:Per your edit, if the caller knows the type that they are passing, could they supply the fully qualified typename as a string as an additional parameter to the service?
编辑:根据您的编辑,如果调用者知道他们传递的类型,他们是否可以将完全限定的类型名作为字符串作为附加参数提供给服务?
If so, you could do this:
如果是这样,你可以这样做:
Type t = Type.GetType(typeName);
and change the Deserialize method to be like this:
并将 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);
}
However, this only gets you an object... If all of the types in question implement a common interface, you could deserialize as above but change the return type to the interface (and cast to it in the return statement)
然而,这只会让你object......如果所有有问题的类型都实现了一个通用接口,你可以像上面那样反序列化,但将返回类型更改为接口(并在 return 语句中转换为它)
回答by H.B.
If you don't mind generics:
如果你不介意泛型:
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;
}
Edit: If you don't know what type of object the XML will translate to you cannot return anything other than an object. You could of course test afterwards what kind of object you just got. One way to do this would probably be to pass all the types of objects that may be deserialized the XmlSerializer.
编辑:如果您不知道 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;
}
Using this method will assume that your object got boxed (you should serialize the same way), which gives you XML like this:
使用此方法将假设您的对象被装箱(您应该以相同的方式序列化),这为您提供了这样的 XML:
<object xsi:type="Person">
...
</object>
(If you have many types to pass you can use reflection to get them, e.g. using something like Assembly.GetExecutingAssembly().GetTypes())
(如果你有很多类型要传递,你可以使用反射来获取它们,例如使用类似的东西Assembly.GetExecutingAssembly().GetTypes())
回答by Alexandru
Forget generics. What if you don't know the return type? If you're using Visual C# 2010 or later, this is the beauty of the new dynamickeyword. Below is an example serializer class that I wrote, and sample usage that takes only the XML string and tries to parse it for a general Systemtype, returning back an output value on success. You can probably extend it for other, more custom types you have, but they probably need to have a default constructor and you would probably need to do some more parsing in order to get the type name and then get the path to it in your assembly. Its a bit tricky but once you get the hang of how the code below works, it starts to open up a bunch of possibilities. Isn't this exactly what you're looking for? Your question asks how to get an object of said type back without knowing that type (note however, that you still have to have a definition of that type in your code in order to deserialize it). Let me explain. If you look at how assemblyFormatterwas used in the code below, you'll see that its trickier for a type you define yourself, like a structor enumfor example, because for these types you'd have to pass assemblyFormatterin as myObject.GetType().FullName. Thats the string that the activator uses to call the default constructor of your type to create it in order to be able to create a serializer off of it; it basically comes down to the complexity of having to know in your assembly that type definition in order to be able to deserialize it.
忘记泛型。如果您不知道返回类型怎么办?如果您使用的是 Visual C# 2010 或更高版本,这就是 newdynamic关键字的美妙之处。下面是我编写的示例序列化程序类,以及仅采用 XML 字符串并尝试解析它的示例用法System类型,成功返回一个输出值。您可能可以将它扩展为您拥有的其他更多自定义类型,但它们可能需要具有默认构造函数,并且您可能需要进行更多解析以获取类型名称,然后在程序集中获取它的路径. 这有点棘手,但是一旦您掌握了下面代码的工作原理,它就会开始开辟一系列可能性。这不正是你要找的吗?您的问题询问如何在不知道该类型的情况下获取所述类型的对象(但是请注意,您仍然必须在代码中定义该类型才能对其进行反序列化)。让我解释。如果您查看assemblyFormatter下面代码中的使用方式,您会发现对于您自己定义的类型,例如 astruct或enum例如,因为对于这些类型,您必须传入assemblyFormatteras 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;
}
}
}
}
}
回答by Bala R
if you have custom serialization/deserialization routine for each type, you could use something like this
如果您对每种类型都有自定义序列化/反序列化例程,则可以使用这样的方法
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
}
...
...
...
}
And you can call
你可以打电话
Person p = Deserialize<Person>(personXmlStringFromClient);
回答by KeithS
How about making a non-generic "front door" function whose purpose is to figure this out? Most XML schemas use the object name, or a reasonable facsimile, as the outermost tag for the object.
如何制作一个非通用的“前门”功能,其目的是解决这个问题?大多数 XML 模式使用对象名称或合理的传真作为对象的最外层标记。

