XmlInclude:列表和数组 [英] XmlInclude : List and array

查看:78
本文介绍了XmlInclude:列表和数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个对象,其变量为object,并且我想将其序列化为XML.

I have an object that have variables as object, and I want to serialize it in XML.

为此,我添加了一些XmlInclude属性,以便管理所有可以使用的类型.

To do so, I've added some XmlInclude attributes in order to manage all the types that can be used.

[Serializable]
[XmlInclude(typeof(short[]))]
[XmlInclude(typeof(ushort[]))]
[XmlInclude(typeof(int[]))]
[XmlInclude(typeof(uint[]))]
[XmlInclude(typeof(ulong[]))]
[XmlInclude(typeof(long[]))]
[XmlInclude(typeof(byte[]))]
[XmlInclude(typeof(decimal[]))]
[XmlInclude(typeof(float[]))]
[XmlInclude(typeof(double[]))]
[XmlInclude(typeof(string[]))]
[XmlInclude(typeof(List<short>))]
[XmlInclude(typeof(List<ushort>))]
[XmlInclude(typeof(List<int>))]
[XmlInclude(typeof(List<uint>))]
[XmlInclude(typeof(List<long>))]
[XmlInclude(typeof(List<ulong>))]
[XmlInclude(typeof(List<byte>))]
[XmlInclude(typeof(List<decimal>))]
[XmlInclude(typeof(List<float>))]
[XmlInclude(typeof(List<double>))]
[XmlInclude(typeof(List<string>))]
[XmlInclude(typeof(MyObject))]
[XmlInclude(typeof(TimeSpan))]
[XmlInclude(typeof(OtherObject))]
[XmlInclude(typeof(MySubObject1))]
[XmlInclude(typeof(MySubObject2))]
[XmlRoot(ElementName = "mc")]
public class MyClass: IComparable
{
    [XmlElement("fm")]
    public object FirstMember;

    [XmlElement("sm")]
    public object SecondMember;

    [XmlElement("tm")]
    public object ThirdMember;
}

我的问题是数组和列表声明不共存.

奇怪的是,如果将数组属性放在首位,则数组成员将被正确序列化,而列表成员将不被序列化.反之亦然.

My issue is that array and list declarations don't coexist.

And weird thing, if the array attributes are placed first, the array members are correctly serialized, but not the list ones. And vice-versa.

自定义类和派生类可以正常工作,但ListArray则不能.我只能找到带有类的示例,但是我使用原始类型.

The custom classes and derived ones work fine, but List and Array don't. I can only find example with classes, but I use primitive types.

有人有主意吗?

PS:我知道我的帖子与

P.S.: I know that my post is similar of this one, but it has no answer since 2011.

推荐答案

我可以重现该问题.发生这种情况是因为XmlSerializer为数组和列表(在这种情况下为字符串)都生成了相同的XML:

I can reproduce the problem. It happens because XmlSerializer generates the same XML for both an array and a list (of strings, in this case):

<mc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <fm xsi:type="ArrayOfString">
        <string>list</string>
        <string>entry</string>
    </fm>
</mc>

由于序列化程序使用相同的 xsi:type 多态名称, "ArrayOfString",对于string[]List<string>,当发现可能会遇到两者的情况时,会抛出异常,因为它无法区分它们.

Since the serializer uses the same xsi:type polymorphic name, "ArrayOfString", for both string[] and List<string>, when it finds a situation where both might be encountered, it throws an exception, since it cannot distinguish between them.

为什么XmlSerializer两者都使用相同的名称?我只能猜测,它可以交换通过序列化不同的集合类型(例如,从List<TElement>SortedSet<TElement>)创建的XML,而无需修复XML文件格式.

Why does does XmlSerializer use the same name for both? I can only guess that it enables interchanging XML created by serializing different collection types (from List<TElement> to SortedSet<TElement>, for instance) without a need to fix the XML file format.

但是,在您的情况下,您需要在XML中区分这些类型的集合,而不是互换它们.因此,您将需要创建某种包装器类,以使其在文件中得以区分.

But, in your case, you need to distinguish between these types of collections in XML, rather than interchange them. Thus you are going to need to create some sort of wrapper class that allows them to be distinguished in the file.

例如,考虑对您的班级进行以下简化:

For instance, consider the following simplification of your class:

[XmlInclude(typeof(string))]
[XmlInclude(typeof(string[]))]
[XmlInclude(typeof(object[]))]
[XmlInclude(typeof(List<string>))]
[XmlInclude(typeof(List<object>))]
[XmlInclude(typeof(SortedSet<string>))]
[XmlInclude(typeof(SortedSet<object>))]
[XmlInclude(typeof(HashSet<string>))]
[XmlInclude(typeof(HashSet<object>))]
[XmlInclude(typeof(LinkedList<string>))]
[XmlInclude(typeof(LinkedList<object>))]
[XmlRoot(ElementName = "mc")]
public class MyClass
{
    [XmlElement("fm")]
    public object FirstMember;
}

此处FirstMember可以包含字符串,字符串或对象的数组或各种类型的字符串或对象的集合.

Here FirstMember can contain a string, an array of strings or objects, or various types of collection of strings or objects.

要为各种类型的集合建立不同的xsi:type值,可以引入以下通用包装类型:

To establish distinct xsi:type values for various types of collection, the following generic wrapper types can be introduced:

/// <summary>
/// Abstract base type for a generic collection wrapper where, to differentiate 
/// between arrays and lists and other types of collections of the same underlying
/// item type, it is necessary to introduce an intermediary type to establish 
/// distinct xsi:type values.
/// </summary>
public abstract class CollectionWrapper
{
    [XmlIgnore]
    public abstract IEnumerable RealCollection { get; }

    static bool TryCreateWrapperType<TElement>(Type actualType, out Type wrapperType)
    {
        if (actualType.IsArray 
            || actualType.IsPrimitive 
            || actualType == typeof(string) 
            || !typeof(IEnumerable).IsAssignableFrom(actualType)
            || actualType == typeof(TElement) // Not polymorphic
            || !actualType.IsGenericType)
        {
            wrapperType = null;
            return false;
        }
        var args = actualType.GetGenericArguments();
        if (args.Length != 1)
        {
            wrapperType = null;
            return false;
        }
        if (actualType.GetGenericTypeDefinition() == typeof(List<>))
        {
            wrapperType = typeof(ListWrapper<>).MakeGenericType(args);
        }
        else if (actualType.GetGenericTypeDefinition() == typeof(HashSet<>))
        {
            wrapperType = typeof(HashSetWrapper<>).MakeGenericType(args);
        }
        else if (actualType.GetGenericTypeDefinition() == typeof(SortedSet<>))
        {
            wrapperType = typeof(SortedSetWrapper<>).MakeGenericType(args);
        }
        else 
        {
            var collectionTypes = actualType.GetCollectionItemTypes().ToList();
            if (collectionTypes.SequenceEqual(args))
                wrapperType = typeof(CollectionWrapper<,>).MakeGenericType(new [] { actualType, args[0] });
            else
            {
                wrapperType = null;
                return false;
            }
        }

        if (!typeof(TElement).IsAssignableFrom(wrapperType))
        {
            wrapperType = null;
            return false;
        }
        return true;
    }

    public static TElement Wrap<TElement>(TElement item)
    {
        if (item == null)
            return item;
        var type = item.GetType();
        if (type == typeof(TElement))
            return item;
        Type wrapperType;
        if (!TryCreateWrapperType<TElement>(type, out wrapperType))
            return item;
        return (TElement)Activator.CreateInstance(wrapperType, item);
    }

    public static TElement Unwrap<TElement>(TElement item)
    {
        if (item is CollectionWrapper)
            return (TElement)((CollectionWrapper)(object)item).RealCollection;
        return item;
    }
}

/// <summary>
/// Generic wrapper type for a generic collection of items.
/// </summary>
/// <typeparam name="TCollection"></typeparam>
/// <typeparam name="TElement"></typeparam>
public class CollectionWrapper<TCollection, TElement> : CollectionWrapper where TCollection : ICollection<TElement>, new()
{
    public class CollectionWrapperEnumerable : IEnumerable<TElement>
    {
        readonly TCollection collection;

        public CollectionWrapperEnumerable(TCollection collection)
        {
            this.collection = collection;
        }

        public void Add(TElement item)
        {
            collection.Add(CollectionWrapper.Unwrap<TElement>(item));
        }

        #region IEnumerable<TElement> Members

        public IEnumerator<TElement> GetEnumerator()
        {
            foreach (var item in collection)
                yield return CollectionWrapper.Wrap(item);
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }

    readonly TCollection collection;
    readonly CollectionWrapperEnumerable enumerable;

    public CollectionWrapper()
        : this(new TCollection())
    {
    }

    public CollectionWrapper(TCollection collection)
    {
        if (collection == null)
            throw new ArgumentNullException();
        this.collection = collection;
        this.enumerable = new CollectionWrapperEnumerable(collection);
    }

    [XmlElement("Item")]
    public CollectionWrapperEnumerable SerializableEnumerable { get { return enumerable; } }

    [XmlIgnore]
    public override IEnumerable RealCollection { get { return collection; } }
}

// These three subclasses of CollectionWrapper for commonly encounterd collections were introduced to improve readability

public class ListWrapper<TElement> : CollectionWrapper<List<TElement>, TElement>
{
    public ListWrapper() : base() { }

    public ListWrapper(List<TElement> list) : base(list) { }
}

public class HashSetWrapper<TElement> : CollectionWrapper<HashSet<TElement>, TElement>
{
    public HashSetWrapper() : base() { }

    public HashSetWrapper(HashSet<TElement> list) : base(list) { }
}

public class SortedSetWrapper<TElement> : CollectionWrapper<SortedSet<TElement>, TElement>
{
    public SortedSetWrapper() : base() { }

    public SortedSetWrapper(SortedSet<TElement> list) : base(list) { }
}

public static class TypeExtensions
{
    /// <summary>
    /// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static IEnumerable<Type> GetCollectionItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }
}

然后在您的简化类中使用,如下所示:

And then used in your simplified class as follows:

[XmlInclude(typeof(string))]
[XmlInclude(typeof(string[]))]
[XmlInclude(typeof(object[]))]
[XmlInclude(typeof(ListWrapper<string>))]
[XmlInclude(typeof(ListWrapper<object>))]
[XmlInclude(typeof(SortedSetWrapper<string>))]
[XmlInclude(typeof(SortedSetWrapper<object>))]
[XmlInclude(typeof(HashSetWrapper<string>))]
[XmlInclude(typeof(HashSetWrapper<object>))]
[XmlInclude(typeof(CollectionWrapper<LinkedList<string>, string>))]
[XmlInclude(typeof(CollectionWrapper<LinkedList<object>, object>))]
[XmlRoot(ElementName = "mc")]
public class MyClass
{
    [XmlElement("fm")]
    [JsonIgnore]
    public object XmlFirstMember
    {
        get
        {
            return CollectionWrapper.Wrap(FirstMember);
        }
        set
        {
            FirstMember = CollectionWrapper.Unwrap(value);
        }
    }

    [XmlIgnore]
    public object FirstMember;
}

然后,获取一个简单的字符串列表:

Then, for a simple list of strings:

var myClass = new MyClass { FirstMember = new List<string> { "list", "entry" } };

将生成以下XML:

<mc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <fm xsi:type="ListWrapperOfString">
        <Item>list</Item>
        <Item>entry</Item>
    </fm>
</mc>

如您所见,xsi:type现在与众不同.

As you can see, the xsi:type is now distinct.

如果我创建以下更复杂的对象:

And if I create the following more complex object:

var myClass = new MyClass
{
    FirstMember = new List<object>
    {
        new List<object> { new List<object> { new List<object> { "hello" } }, "there" },
        new HashSet<string> { "hello", "hello", "there" },
        new SortedSet<string> { "hello", "hello", "there" },
        new LinkedList<object>( new object [] { new LinkedList<string>( new [] { "hello", "there" }) }),
    }
};

将生成以下XML:

<mc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <fm xsi:type="ListWrapperOfObject">
        <Item xsi:type="ListWrapperOfObject">
            <Item xsi:type="ListWrapperOfObject">
                <Item xsi:type="ListWrapperOfObject">
                    <Item xsi:type="xsd:string">hello</Item>
                </Item>
            </Item>
            <Item xsi:type="xsd:string">there</Item>
        </Item>
        <Item xsi:type="HashSetWrapperOfString">
            <Item>hello</Item>
            <Item>there</Item>
        </Item>
        <Item xsi:type="SortedSetWrapperOfString">
            <Item>hello</Item>
            <Item>there</Item>
        </Item>
        <Item xsi:type="CollectionWrapperOfLinkedListOfObjectObject">
            <Item xsi:type="CollectionWrapperOfLinkedListOfStringString">
                <Item>hello</Item>
                <Item>there</Item>
            </Item>
        </Item>
    </fm>
</mc>

每种不同的收藏类型如何都有自己独特的xsi:type.

Each different collection type how has its own distinct xsi:type.

这篇关于XmlInclude:列表和数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆