Подтвердить что ты не робот

Могу ли я сериализовать анонимных типов как xml?

Я понял, что анонимные типы отмечены частным компилятором, а свойства доступны только для чтения. Есть ли способ сериализации их в xml (без десериализации)? Он работает с JSON, как я могу это сделать с XML?

4b9b3361

Ответ 1

Этого нельзя достичь ни с помощью XmlSerializer, ни DataContractSerializer. Это можно сделать с помощью написанного вручную кода, как показано ниже (я не могу комментировать, является ли код достаточно полным, чтобы обрабатывать все типы - но это очень хорошее начало).

Ответ 2

Что-то вроде этого должно заставить вас начать...

class Program
{
    static void Main(string[] args)
    {
        var me = new
        {
            Hello = "World",
            Other = new
            {
                My = "Object",
                V = 1,
                B = (byte)2
            }
        };

        var x = me.ToXml();
    }
}
public static class Tools
{
    private static readonly Type[] WriteTypes = new[] {
        typeof(string), typeof(DateTime), typeof(Enum), 
        typeof(decimal), typeof(Guid),
    };
    public static bool IsSimpleType(this Type type)
    {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
    public static XElement ToXml(this object input)
    {
        return input.ToXml(null);
    }
    public static XElement ToXml(this object input, string element)
    {
        if (input == null)
            return null;

        if (string.IsNullOrEmpty(element))
            element = "object";
        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null)
        {
            var type = input.GetType();
            var props = type.GetProperties();

            var elements = from prop in props
                           let name = XmlConvert.EncodeName(prop.Name)
                           let val = prop.GetValue(input, null)
                           let value = prop.PropertyType.IsSimpleType()
                                ? new XElement(name, val)
                                : val.ToXml(name)
                           where value != null
                           select value;

            ret.Add(elements);
        }

        return ret;
    }
}

... result xml...

<object>
  <Hello>World</Hello>
  <Other>
    <My>Object</My>
    <V>1</V>
    <B>2</B>
  </Other>
</object>

Ответ 3

Спасибо, отличная работа @Matthew и @Martin.

Я сделал пару модификаций для размещения Nullables и Enums. Также я изменил его так, чтобы элементы массива были названы в соответствии с именем свойства + index.

Вот код, если кому-то интересно

public static class ObjectExtensions {
    #region Private Fields
    private static readonly Type[] WriteTypes = new[] {
        typeof(string), typeof(DateTime), typeof(Enum), 
        typeof(decimal), typeof(Guid),
    };
    #endregion Private Fields
    #region .ToXml
    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <returns>Returns the object as it XML representation in an XElement.</returns>
    public static XElement ToXml(this object input) {
        return input.ToXml(null);
    }

    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <param name="element">The element name.</param>
    /// <returns>Returns the object as it XML representation in an XElement.</returns>
    public static XElement ToXml(this object input, string element) {
        return _ToXml(input, element);
    }

    private static XElement _ToXml(object input, string element, int? arrayIndex = null, string arrayName = null) {
        if (input == null)
            return null;

        if (String.IsNullOrEmpty(element)) {
            string name = input.GetType().Name;
            element = name.Contains("AnonymousType") 
                ? "Object" 
                : arrayIndex != null
                    ? arrayName + "_" + arrayIndex
                    : name;
        }

        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null) {
            var type = input.GetType();
            var props = type.GetProperties();

            var elements = props.Select(p => {
                var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
                var name = XmlConvert.EncodeName(p.Name);
                var val = pType.IsArray ? "array" : p.GetValue(input, null);
                var value = pType.IsArray 
                    ? GetArrayElement(p, (Array)p.GetValue(input, null))
                    : pType.IsSimpleType() || pType.IsEnum 
                        ? new XElement(name, val) 
                        : val.ToXml(name);
                return value;
            })
            .Where(v=>v !=null);

            ret.Add(elements);
        }

        return ret;
    }

    #region helpers
    /// <summary>
    /// Gets the array element.
    /// </summary>
    /// <param name="info">The property info.</param>
    /// <param name="input">The input object.</param>
    /// <returns>Returns an XElement with the array collection as child elements.</returns>
    private static XElement GetArrayElement(PropertyInfo info, Array input) {
        var name = XmlConvert.EncodeName(info.Name);

        XElement rootElement = new XElement(name);

        var arrayCount = input == null ? 0 : input.GetLength(0);

        for (int i = 0; i < arrayCount; i++) {
            var val = input.GetValue(i);
            XElement childElement = val.GetType().IsSimpleType() ? new XElement(name + "_" + i, val) : _ToXml(val, null, i, name);

            rootElement.Add(childElement);
        }

        return rootElement;
    }

    #region .IsSimpleType
    public static bool IsSimpleType(this Type type) {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
    #endregion .IsSimpleType

    #endregion helpers
    #endregion .ToXml
}

Ответ 4

Моя собственная версия отличной работы @Matthew и @Martin: теперь поддерживаются массивы перечислений и понятие массивов в обобщенном виде в IEnumerable, чтобы также поддерживать всевозможные коллекции.

public static class ObjectExtensions {
/// <summary>
/// Converts an anonymous type to an XElement.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>Returns the object as it XML representation in an XElement.</returns>
public static XElement ToXml2(this object input) {
    return input.ToXml2(null);
}

/// <summary>
/// Converts an anonymous type to an XElement.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="element">The element name.</param>
/// <returns>Returns the object as it XML representation in an XElement.</returns>
public static XElement ToXml2(this object input, string element) {
    return _ToXml(input, element);
}

private static XElement _ToXml(object input, string element, int? arrayIndex = null, string arrayName = null) {
    if (input == null)
        return null;

    if (String.IsNullOrEmpty(element)) {
        string name = input.GetType().Name;
        element = name.Contains("AnonymousType") 
            ? "Object" 
            : arrayIndex != null
                ? arrayName + "_" + arrayIndex
                : name;
    }

    element = XmlConvert.EncodeName(element);
    var ret = new XElement(element);

    if (input != null) {
        var type = input.GetType();
        var props = type.GetProperties();

        var elements = props.Select(p => {
            var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
            var name = XmlConvert.EncodeName(p.Name);
            var val = pType.IsArray ? "array" : p.GetValue(input, null);
            var value = pType.IsEnumerable()
                ? GetEnumerableElements(p, (IEnumerable)p.GetValue(input, null))
                : pType.IsSimpleType2() || pType.IsEnum 
                    ? new XElement(name, val) 
                    : val.ToXml2(name);
            return value;
        })
        .Where(v=>v !=null);

        ret.Add(elements);
    }

    return ret;
}

#region helpers

private static XElement GetEnumerableElements(PropertyInfo info, IEnumerable input) {
    var name = XmlConvert.EncodeName(info.Name);

    XElement rootElement = new XElement(name);

    int i = 0;
    foreach(var v in input)
    {
        XElement childElement = v.GetType().IsSimpleType2() || v.GetType().IsEnum ? new XElement(name + "_" + i, v) : _ToXml(v, null, i, name);
        rootElement.Add(childElement);
        i++;
    }
    return rootElement;
}

private static readonly Type[] WriteTypes = new[] {
    typeof(string), typeof(DateTime), typeof(Enum), 
    typeof(decimal), typeof(Guid),
};
public static bool IsSimpleType2(this Type type) {
    return type.IsPrimitive || WriteTypes.Contains(type);
}

private static readonly Type[] FlatternTypes = new[] {
    typeof(string)
};
public static bool IsEnumerable(this Type type) {
    return typeof(IEnumerable).IsAssignableFrom(type) && !FlatternTypes.Contains(type);
}
#endregion
}

Ответ 5

Нижеприведенный ответ обрабатывает IEnumerables в том виде, в котором я нуждался, и сделаю следующее:

new
{
    Foo = new[]
    {
        new { Name = "One" },
        new { Name = "Two" },
    },
    Bar = new[]
    {
        new { Name = "Three" },
        new { Name = "Four" },
    },
}

в это:

<object>
    <Foo><Name>One</Name></Foo>
    <Foo><Name>Two</Name></Foo>
    <Bar><Name>Three</Name></Bar>
    <Bar><Name>Four</Name></Bar>
</object>

Итак, вот и вы, еще один вариант ответа Мэтью:

public static class Tools
{
    private static readonly Type[] WriteTypes = new[] {
        typeof(string),
        typeof(Enum),
        typeof(DateTime), typeof(DateTime?),
        typeof(DateTimeOffset), typeof(DateTimeOffset?),
        typeof(int), typeof(int?),
        typeof(decimal), typeof(decimal?),
        typeof(Guid), typeof(Guid?),
    };
    public static bool IsSimpleType(this Type type)
    {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
    public static object ToXml(this object input)
    {
        return input.ToXml(null);
    }
    public static object ToXml(this object input, string element)
    {
        if (input == null)
            return null;

        if (string.IsNullOrEmpty(element))
            element = "object";
        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null)
        {
            var type = input.GetType();

            if (input is IEnumerable && !type.IsSimpleType())
            {
                var elements = (input as IEnumerable<object>)
                    .Select(m => m.ToXml(element))
                    .ToArray();

                return elements;
            }
            else
            {
                var props = type.GetProperties();

                var elements = from prop in props
                               let name = XmlConvert.EncodeName(prop.Name)
                               let val = prop.GetValue(input, null)
                               let value = prop.PropertyType.IsSimpleType()
                                    ? new XElement(name, val)
                                    : val.ToXml(name)
                               where value != null
                               select value;

                ret.Add(elements);
            }
        }

        return ret;
    }
}

Ответ 6

Я знаю, что это старый пост, но мое решение преобразует анонимный тип в XML всего за 2 строки кода.

Сначала преобразуйте анонимный тип в JSON, а затем из JSON в XML.

var jsonText = JsonConvert.SerializeObject(data);           // convert to JSON
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText); // convert JSON to XML Document

Образец

var data = new       // data - Anonymous Type
{
    Request = new
    {
        OrderNumber = 123,
        Note = "Hello World"
    }
};

var jsonText = JsonConvert.SerializeObject(data);           
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText);

Console.WriteLine(doc.OuterXml);                            

Выход

<Request>
    <OrderNumber>123</OrderNumber>
    <Note>Hello World</Note>
</Request>