Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
306 views
in Technique[技术] by (71.8m points)

c# - XmlInclude : List and array

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...