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

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

问题描述

我有一个具有 object 变量的对象,我想用 XML 序列化它.

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

[可序列化][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))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(MyObject))][XmlInclude(typeof(TimeSpan))][XmlInclude(typeof(OtherObject))][XmlInclude(typeof(MySubObject1))][XmlInclude(typeof(MySubObject2))][XmlRoot(ElementName = "mc")]公共类 MyClass:IComparable{[XmlElement("fm")]公共对象 FirstMember;[XmlElement("sm")]公共对象 SecondMember;[XmlElement("tm")]公共对象ThirdMember;}

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

奇怪的是,如果首先放置数组属性,则数组成员会正确序列化,但不会正确序列化列表成员.反之亦然.

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

有人有想法吗?

PS:我知道我的帖子类似于 这个,但自 2011 年以来一直没有答案.

解决方案

我可以重现该问题.这是因为 XmlSerializer 为数组和列表(在本例中为字符串)生成相同的 XML:

<块引用>

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

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

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

但是,就您而言,您需要在 XML 中区分这些类型的集合,而不是交换它们.因此,您将需要创建某种包装类,以便在文件中区分它们.

例如,考虑以下对您的类的简化:

[XmlInclude(typeof(string))][XmlInclude(typeof(string[]))][XmlInclude(typeof(object[]))][XmlInclude(typeof(List))][XmlInclude(typeof(List))][XmlInclude(typeof(SortedSet))][XmlInclude(typeof(SortedSet))][XmlInclude(typeof(HashSet))][XmlInclude(typeof(HashSet))][XmlInclude(typeof(LinkedList))][XmlInclude(typeof(LinkedList))][XmlRoot(ElementName = "mc")]公开课 MyClass{[XmlElement("fm")]公共对象 FirstMember;}

这里的FirstMember 可以包含一个字符串、一个字符串或对象的数组,或者各种类型的字符串或对象的集合.

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

////<摘要>///泛型集合包装器的抽象基类型,用于区分///数组和列表以及其他类型相同底层的集合之间///item类型,需要引入一个中介类型来建立///不同的 xsi:type 值.///</总结>公共抽象类 CollectionWrapper{[XmlIgnore]公共抽象 IEnumerable RealCollection { 获取;}static bool TryCreateWrapperType(Type actualType, out Type wrapperType){如果 (actualType.IsArray||实际类型.IsPrimitive||实际类型 == 类型(字符串)||!typeof(IEnumerable).IsAssignableFrom(actualType)||actualType == typeof(TElement)//不是多态的||!actualType.IsGenericType){包装类型 = 空;返回假;}var args = actualType.GetGenericArguments();如果(参数.长度!= 1){包装类型 = 空;返回假;}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);}别的{var collectionTypes = actualType.GetCollectionItemTypes().ToList();if (collectionTypes.SequenceEqual(args))wrapperType = typeof(CollectionWrapper<,>).MakeGenericType(new [] { actualType, args[0] });别的{包装类型 = 空;返回假;}}if (!typeof(TElement).IsAssignableFrom(wrapperType)){包装类型 = 空;返回假;}返回真;}public static TElement Wrap(TElement item){如果(项目==空)归还物品;var type = item.GetType();if (type == typeof(TElement))归还物品;类型包装器类型;if (!TryCreateWrapperType(type, out wrapperType))归还物品;return (TElement)Activator.CreateInstance(wrapperType, item);}public static TElement Unwrap(TElement item){如果(项目是 CollectionWrapper)return (TElement)((CollectionWrapper)(object)item).RealCollection;归还物品;}}///<总结>///通用项目集合的通用包装器类型.///</总结>///<typeparam name="TCollection"></typeparam>///<typeparam name="TElement"></typeparam>公共类 CollectionWrapper: CollectionWrapper where TCollection : ICollection, new(){公共类 CollectionWrapperEnumerable : IEnumerable{只读 TCollection 集合;公共 CollectionWrapperEnumerable(TCollection 集合){this.collection = 集合;}公共无效添加(TElement 项目){collection.Add(CollectionWrapper.Unwrap(item));}#region IEnumerable会员公共 IEnumerator获取枚举器(){foreach(集合中的var 项目)收益率返回 CollectionWrapper.Wrap(item);}#endregion#region IEnumerable 成员IEnumerator IEnumerable.GetEnumerator(){返回 GetEnumerator();}#endregion}只读 TCollection 集合;只读 CollectionWrapperEnumerable 可枚举;公共 CollectionWrapper():这个(新的TCollection()){}公共集合包装器(TCollection 集合){如果(集合 == 空)抛出新的 ArgumentNullException();this.collection = 集合;this.enumerable = new CollectionWrapperEnumerable(collection);}[XmlElement("项目")]公共 CollectionWrapperEnumerable SerializableEnumerable { get { return enumerable;} }[XmlIgnore]公共覆盖 IEnumerable RealCollection { 获取 { 返回集合;} }}//为常见的集合引入了CollectionWrapper的这三个子类,以提高可读性公共类 ListWrapper: CollectionWrapper, TElement>{公共 ListWrapper() : base() { }public ListWrapper(List list) : base(list) { }}公共类 HashSetWrapper: CollectionWrapper, TElement>{公共 HashSetWrapper() : base() { }public HashSetWrapper(HashSet list) : base(list) { }}公共类 SortedSetWrapper: CollectionWrapper, TElement>{公共 SortedSetWrapper() : base() { }public SortedSetWrapper(SortedSet list) : base(list) { }}公共静态类 TypeExtensions{///<总结>///返回传入类型实现的所有接口以及类型本身,如果它是一个接口.///</总结>///<param name="type"></param>///<returns></returns>公共静态 IEnumerableGetInterfacesAndSelf(此类型类型){如果(类型==空)抛出新的 ArgumentNullException();如果(类型.IsInterface)return new[] { type }.Concat(type.GetInterfaces());别的返回类型.GetInterfaces();}公共静态 IEnumerableGetCollectionItemTypes(此类型类型){foreach(在 type.GetInterfacesAndSelf() 中输入 intType){如果 (intType.IsGenericType&&intType.GetGenericTypeDefinition() == typeof(IEnumerable<>)){yield return intType.GetGenericArguments()[0];}}}}

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

[XmlInclude(typeof(string))][XmlInclude(typeof(string[]))][XmlInclude(typeof(object[]))][XmlInclude(typeof(ListWrapper))][XmlInclude(typeof(ListWrapper))][XmlInclude(typeof(SortedSetWrapper))][XmlInclude(typeof(SortedSetWrapper))][XmlInclude(typeof(HashSetWrapper))][XmlInclude(typeof(HashSetWrapper))][XmlInclude(typeof(CollectionWrapper, string>))][XmlInclude(typeof(CollectionWrapper, object>))][XmlRoot(ElementName = "mc")]公开课 MyClass{[XmlElement("fm")][Json忽略]公共对象 XmlFirstMember{得到{返回 CollectionWrapper.Wrap(FirstMember);}放{FirstMember = CollectionWrapper.Unwrap(value);}}[XmlIgnore]公共对象 FirstMember;}

然后,对于一个简单的字符串列表:

var myClass = new MyClass { FirstMember = new List{列表",条目"}};

生成以下 XML:

<块引用>

<mc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><fm xsi:type="ListWrapperOfString"><项目>列表</项目><项目>条目</项目></fm></mc>

如您所见,xsi:type 现在是不同的.

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

var myClass = new MyClass{FirstMember = 新列表<对象>{新列表<对象>{ 新列表<对象>{ 新列表<对象>{ 你好呀" },新的哈希集<字符串>{你好",你好",那里"},新的 SortedSet{你好",你好",那里"},new LinkedList( new object [] { new LinkedList( 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">你好</Item></项目></项目><Item xsi:type="xsd:string">那里</Item></项目><Item xsi:type="HashSetWrapperOfString"><物品>你好</物品><物品>那里</物品></项目><Item xsi:type="SortedSetWrapperOfString"><物品>你好</物品><物品>那里</物品></项目><Item xsi:type="CollectionWrapperOfLinkedListOfObjectObject"><Item xsi:type="CollectionWrapperOfLinkedListOfStringString"><物品>你好</物品><物品>那里</物品></项目></项目></fm></mc>

每个不同的集合类型都有自己独特的xsi:type.

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

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.

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.

Does anyone have an idea ?

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

解决方案

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>

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.

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.

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

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

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

The following XML is generated:

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

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

The following XML is generated:

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

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

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

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