XmlInclude:列表和数组 [英] XmlInclude : List and array
问题描述
我有一个对象,其变量为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.
自定义类和派生类可以正常工作,但List
和Array
则不能.我只能找到带有类的示例,但是我使用原始类型.
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.
有人有主意吗?
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屋!