Общая десериализация строки xml
у меня есть куча разных классов DTO. В какой-то момент они сериализуются в строку XML и переносятся на клиентскую сторону веб-приложения. Теперь, когда клиент отстреливает строку XML, мне нужно десериализовать ее обратно в экземпляр класса DTO, который она представляет. Проблема в том, что я хочу сделать его универсальным и, возможно, функцией, которая принимает строку xml и выплевывает объект типа. Что-то вроде длинных этих строк:
public sometype? Deserialize (string xml)
{
//some code here
return objectFromXml;
}
изменить: Ужасный пример! Я просто противоречил себе!
Я не могу сделать следующее:
Person person = Deserialize(personXmlStringFromClient);
потому что я не знаю, что personXmlStringFromClient является представлением экземпляра объекта Person DTO.
Я не знаю, какой сериализованный объект дается мне, и это, кажется, моя проблема здесь. Я читал об отражении и других методах, которые включают вставку типа в xml, чтобы десериализатор знал, что с ним делать. Кажется, я не могу тянуть. все это вместе в одно целое. Кроме того, в большинстве примеров автор знает, какой тип будет после десериализации. Любое предложение приветствуется! Если мне нужно сделать что-то особенное с процессом сериализации, пожалуйста, поделитесь этим тоже.
5 ответов
вы можете использовать универсальную:
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 как.
Edit: в вашем редактировании, если вызывающий знает тип, который они передают, могут ли они предоставить полное имя типа в качестве строки в качестве дополнительного параметра для службы?
если это так, вы можете сделать это:
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);
}
однако, это только получает вас object
... Если все типы, о которых идет речь, реализуют общий интерфейс, вы можете десериализоваться как выше, но измените тип возврата на интерфейс (и приведите к нему в инструкции return)
забудьте дженериков. Что если вы не знаете тип возврата? Если вы используете Visual C# 2010 или более поздней версии, это красота нового dynamic
ключевое слово. Ниже приведен пример класса сериализатора, который я написал, и пример использования, который принимает только строку XML и пытается разобрать ее для общего System
введите, возвращая выходное значение при успешном выполнении. Вероятно, вы можете расширить его для других, более пользовательских типов, но они, вероятно, должны иметь конструктор по умолчанию, и вам, вероятно, понадобится выполните еще несколько синтаксических анализов, чтобы получить имя типа, а затем получить путь к нему в сборке. Это немного сложно, но как только вы поймете, как работает код ниже, он начинает открывать кучу возможностей. Разве это не то, что ты ищешь? Ваш вопрос спрашивает, Как вернуть объект указанного типа, не зная этого типа (обратите внимание, однако, что вам все равно нужно иметь определение этого типа в коде, чтобы десериализовать его). Позвольте мне объяснить. Если вы посмотрите на то, как assemblyFormatter
был использован в коде ниже, вы увидите, что его сложнее для типа, который вы определяете сами, как struct
или enum
например, потому что для этих типов вам придется пройти assemblyFormatter
в as 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;
}
}
}
}
}
Как насчет создания неродовой функции "передней двери", целью которой является выяснить это? Большинство XML-схем используют имя объекта или разумное факсимиле в качестве внешнего тега для объекта.
Если у вас есть пользовательская процедура сериализации/десериализации для каждого типа, вы можете использовать что-то вроде этого
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);
если вы не против дженериков:
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: если вы не знаете, какой тип объекта переведет 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()
)