Общая десериализация строки XML - PullRequest
14 голосов
/ 14 февраля 2011

У меня есть куча разных классов DTO.В какой-то момент они сериализуются в XML-строку и переносятся на клиентскую часть веб-приложения.Теперь, когда клиент выполняет откат XML-строки, мне нужно десериализовать ее обратно в экземпляр класса DTO, который он представляет.Проблема в том, что я хочу сделать его универсальным и, возможно, функцией, которая принимает строку XML и выплевывает объект типа.Что-то вроде длинных этих строк:

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

РЕДАКТИРОВАТЬ: Ужасный пример!Я просто противоречил сам себе!

Я не могу сделать следующее:

Person person = Deserialize(personXmlStringFromClient);

, потому что я не знаю, что personXmlStringFromClient является представлением экземпляра объекта Person DTO.не знаю, какой сериализованный объект мне дан, и это, кажется, моя проблема здесь.Я читал об отражении и других методах, которые включают вставку типа в xml, чтобы десериализатор знал, что с ним делать.Я не могу собрать все это в один рабочий кусок.Кроме того, в большинстве случаев автор знает, какой тип будет после десериализации.Любое предложение приветствуется!Если мне нужно сделать что-то особенное с процессом сериализации, пожалуйста, поделитесь этим тоже.

Ответы [ 5 ]

22 голосов
/ 14 февраля 2011

Вы можете использовать универсальный:

    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);

и изменить метод десериализации следующим образом:

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 ... Если все рассматриваемые типы реализуют общий интерфейс, вы можете десериализовать, как указано выше, но изменить тип возвращаемого значения на интерфейс (и привести к нему в операторе возврата)

1 голос
/ 11 мая 2014

Забудьте дженерики. Что если вы не знаете тип возвращаемого значения? Если вы используете 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;
                }
            }
        }
    }
}
1 голос
/ 14 февраля 2011

Если вы не возражаете против обобщений:

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())

0 голосов
/ 14 февраля 2011

если у вас есть пользовательская процедура сериализации / десериализации для каждого типа, вы можете использовать что-то вроде этого

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);
0 голосов
/ 14 февраля 2011

Как насчет создания неуниверсальной функции "парадной двери", целью которой является выяснить это?Большинство XML-схем используют имя объекта или приемлемый факсимильный аппарат в качестве внешнего тега для объекта.

...