Общая десериализация строки xml

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

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

EDIT: Ужасный пример! Я просто противоречил себе!

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

Person person = Deserialize(personXmlStringFromClient);

потому что я не знаю, что personXmlStringFromClient является представлением экземпляра объекта Person DTO.

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

Ответ 1

Вы можете использовать общий:

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

Однако это приведет только к object... Если все типы, о которых идет речь, реализуют общий интерфейс, вы можете десериализовать, как указано выше, но изменить тип возврата на интерфейс (и передать его в оператор return )

Ответ 2

Забудьте о дженериках. Что делать, если вы не знаете тип возврата? Если вы используете Visual С# 2010 или более позднюю версию, это красота нового ключевого слова dynamic. Ниже приведен пример класса serializer, который я написал, и пример использования, который использует только строку XML и пытается проанализировать его для общего типа System, возвращая возвращаемое значение при успешном завершении. Вы, вероятно, можете расширить его для других, более настраиваемых типов, которые у вас есть, но у них, вероятно, должен быть конструктор по умолчанию, и вам, вероятно, понадобится еще немного разбора, чтобы получить имя типа, а затем получить путь к нему в вашей сборке, Это немного сложно, но как только вы узнаете, как работает код ниже, он начинает открывать кучу возможностей. Разве это не то, что вы ищете? Ваш вопрос спрашивает, как вернуть объект указанного типа обратно, не зная этого типа (обратите внимание, однако, что вы все еще должны иметь определение этого типа в своем коде, чтобы десериализовать его). Позволь мне объяснить. Если вы посмотрите, как assemblyFormatter был использован в приведенном ниже коде, вы увидите, что его сложнее для типа, который вы определяете самостоятельно, например, struct или enum, потому что для этих типов вам придется pass 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 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 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;
                }
            }
        }
    }
}

Ответ 3

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

Ответ 4

Если вы не возражаете против дженериков:

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

Ответ 5

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

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