如何(反)序列化具有字符串数组值的可序列化字典?
How do I (de)serialize a serializable dictionary with string array values?
我需要在 C# (.NET Framework 4.5.2) 中(反)序列化一个 class 到 XML ,它有一个字典 属性 和 string
键和 string[]
数组值。我在另一个问题上使用 this answer 中提到的 SerializableDictionary<TKey, TValue>
实现,所以这意味着我的 属性 是 SerializableDictionary<string, string[]>
.
类型
将其序列化为 XML 文件看起来工作正常;但是,反序列化总是失败并显示 System.InvalidOperationException.
即使只是反序列化字典本身也会发生这种情况。请参阅下面重现问题的 MSTest 单元测试:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using My.Namespace.Collections; // Where SerializableDictionary is defined.
using System.Xml.Serialization;
using System.IO;
using System.Linq;
namespace My.Namespace.Collections.Tests
{
/// <summary>
/// Provides unit test methods for the <see cref="SerializableDictionary{TKey, TValue}"/> class.
/// </summary>
[TestClass]
public class SerializableDictionaryTests
{
/// <summary>
/// A basic test that the <see cref="SerializableDictionary{TKey, TValue}"/> class has working
/// (de)serialization.
/// </summary>
[TestMethod]
[Timeout(100)]
public void SerializableDictionary_BasicTest()
{
// Arrange.
var sourceDictionary = new SerializableDictionary<string, string[]>();
SerializableDictionary<string, string[]> destinationDictionary;
var serializer = new XmlSerializer(typeof(SerializableDictionary<string, string[]>));
var list1 = new string[] { "apple", "banana", "pear" };
var list2 = new string[] { "carrot", "broccoli", "cauliflower", "onion" };
var list3 = new string[] { "beef", "chicken" };
sourceDictionary.Add("fruits", list1);
sourceDictionary.Add("vegetables", list2);
sourceDictionary.Add("meats", list3);
// Act.
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, sourceDictionary);
stream.Position = 0;
destinationDictionary = (SerializableDictionary<string, string[]>)serializer.Deserialize(stream);
}
// Assert.
// NOTE: We don't get this far because it crashes on the last statement above in the using block.
Assert.AreEqual(3, destinationDictionary.Keys.Count);
Assert.IsTrue(destinationDictionary.ContainsKey("fruits"));
Assert.IsTrue(destinationDictionary.ContainsKey("vegetables"));
Assert.IsTrue(destinationDictionary.ContainsKey("meats"));
Assert.AreEqual(3, destinationDictionary["fruits"].Length);
Assert.IsTrue(destinationDictionary["fruits"].Contains("apple"));
Assert.IsTrue(destinationDictionary["fruits"].Contains("banana"));
Assert.IsTrue(destinationDictionary["fruits"].Contains("pear"));
Assert.AreEqual(4, destinationDictionary["vegetables"].Length);
Assert.IsTrue(destinationDictionary["vegetables"].Contains("carrot"));
Assert.IsTrue(destinationDictionary["vegetables"].Contains("broccoli"));
Assert.IsTrue(destinationDictionary["vegetables"].Contains("cauliflower"));
Assert.IsTrue(destinationDictionary["vegetables"].Contains("onion"));
Assert.AreEqual(2, destinationDictionary["meats"].Length);
Assert.IsTrue(destinationDictionary["meats"].Contains("beef"));
Assert.IsTrue(destinationDictionary["meats"].Contains("chicken"));
}
}
}
异常发生在 serializer.Deserialize
调用处,内容为:
Test method My.Namespace.Collections.Tests.SerializableDictionaryTests.SerializableDictionary_BasicTest threw exception:
System.InvalidOperationException: There is an error in XML document (8, 8). ---> System.InvalidOperationException: There is an error in XML document (8, 8). ---> System.InvalidOperationException: <ArrayOfString xmlns=''> was not expected.
为什么我会收到此异常,我该如何避免?如果可能的话,我想避免只为这个 属性 求助于自定义 XML 序列化。
编辑#1:
我制作了一个运行上述构建和序列化代码的小型控制台程序,然后使用 FileStream
class 将生成的 XML 写入文件。这是它的内容:
<?xml version="1.0"?>
<dictionary>
<item>
<key>
<string>fruits</string>
</key>
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>apple</string>
<string>banana</string>
<string>pear</string>
</ArrayOfString>
</value>
</item>
<item>
<key>
<string>vegetables</string>
</key>
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>carrot</string>
<string>broccoli</string>
<string>cauliflower</string>
<string>onion</string>
</ArrayOfString>
</value>
</item>
<item>
<key>
<string>meats</string>
</key>
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>beef</string>
<string>chicken</string>
</ArrayOfString>
</value>
</item>
</dictionary>
编辑#2:
在我的测试中将 SerializableDictionary<TKey, TValue>
声明的 TValue
类型更改为不同的东西并检查它是否有效后,我可以确认如果 TValue
是 string
,但是如果将它设置为我的自定义可序列化 class,即使 class 装饰有 SerializableAttribute
或实现 IXmlSerializable
接口。所以,问题实际上比字符串数组更大。
在为此苦苦挣扎了几天之后,我得出的结论是,我使用的 Serializable<TKey, TValue>
实现无法处理数组和其他对象。
我求助于在 class 上实现 IXmlSerializable
接口,在 WriteXml
方法中将字典值序列化为列表。在 ReadXml
中,我简单地反序列化存储的列表,然后将其值放回字典中,同时检查列表项上是否没有作为附加 属性 存储的重复键。它很笨重,但它确实有效。
很高兴您找到了解决方案并成功运行。不确定您是否仍然对解决方案持开放态度。尽管如此,您可以按照原样使用 Dictionary
进行以下操作。
尽管 .NET 的 XmlSerializer
不支持字典,但有一些库可以为您完成。一个这样的库是 SharpSerializer (Source, NuGet),它将任何类型序列化为二进制或 XML。
用法 就像:
var serializer = new SharpSerializer();
serializer.Serialize(dict, "test.xml");
输入
var dict = new Dictionary<string, string[]>
{
{ "nikhil", new string[] { "nikhil.0@so.com", "nikhil.1@so.com" } }
};
输出
<Dictionary name="Root" type="System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<Items>
<Item>
<Simple value="nikhil" />
<SingleArray>
<Items>
<Simple value="nikhil.0@so.com" />
<Simple value="nikhil.1@so.com" />
</Items>
</SingleArray>
</Item>
</Items>
</Dictionary>
我需要在 C# (.NET Framework 4.5.2) 中(反)序列化一个 class 到 XML ,它有一个字典 属性 和 string
键和 string[]
数组值。我在另一个问题上使用 this answer 中提到的 SerializableDictionary<TKey, TValue>
实现,所以这意味着我的 属性 是 SerializableDictionary<string, string[]>
.
将其序列化为 XML 文件看起来工作正常;但是,反序列化总是失败并显示 System.InvalidOperationException.
即使只是反序列化字典本身也会发生这种情况。请参阅下面重现问题的 MSTest 单元测试:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using My.Namespace.Collections; // Where SerializableDictionary is defined.
using System.Xml.Serialization;
using System.IO;
using System.Linq;
namespace My.Namespace.Collections.Tests
{
/// <summary>
/// Provides unit test methods for the <see cref="SerializableDictionary{TKey, TValue}"/> class.
/// </summary>
[TestClass]
public class SerializableDictionaryTests
{
/// <summary>
/// A basic test that the <see cref="SerializableDictionary{TKey, TValue}"/> class has working
/// (de)serialization.
/// </summary>
[TestMethod]
[Timeout(100)]
public void SerializableDictionary_BasicTest()
{
// Arrange.
var sourceDictionary = new SerializableDictionary<string, string[]>();
SerializableDictionary<string, string[]> destinationDictionary;
var serializer = new XmlSerializer(typeof(SerializableDictionary<string, string[]>));
var list1 = new string[] { "apple", "banana", "pear" };
var list2 = new string[] { "carrot", "broccoli", "cauliflower", "onion" };
var list3 = new string[] { "beef", "chicken" };
sourceDictionary.Add("fruits", list1);
sourceDictionary.Add("vegetables", list2);
sourceDictionary.Add("meats", list3);
// Act.
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, sourceDictionary);
stream.Position = 0;
destinationDictionary = (SerializableDictionary<string, string[]>)serializer.Deserialize(stream);
}
// Assert.
// NOTE: We don't get this far because it crashes on the last statement above in the using block.
Assert.AreEqual(3, destinationDictionary.Keys.Count);
Assert.IsTrue(destinationDictionary.ContainsKey("fruits"));
Assert.IsTrue(destinationDictionary.ContainsKey("vegetables"));
Assert.IsTrue(destinationDictionary.ContainsKey("meats"));
Assert.AreEqual(3, destinationDictionary["fruits"].Length);
Assert.IsTrue(destinationDictionary["fruits"].Contains("apple"));
Assert.IsTrue(destinationDictionary["fruits"].Contains("banana"));
Assert.IsTrue(destinationDictionary["fruits"].Contains("pear"));
Assert.AreEqual(4, destinationDictionary["vegetables"].Length);
Assert.IsTrue(destinationDictionary["vegetables"].Contains("carrot"));
Assert.IsTrue(destinationDictionary["vegetables"].Contains("broccoli"));
Assert.IsTrue(destinationDictionary["vegetables"].Contains("cauliflower"));
Assert.IsTrue(destinationDictionary["vegetables"].Contains("onion"));
Assert.AreEqual(2, destinationDictionary["meats"].Length);
Assert.IsTrue(destinationDictionary["meats"].Contains("beef"));
Assert.IsTrue(destinationDictionary["meats"].Contains("chicken"));
}
}
}
异常发生在 serializer.Deserialize
调用处,内容为:
Test method My.Namespace.Collections.Tests.SerializableDictionaryTests.SerializableDictionary_BasicTest threw exception:
System.InvalidOperationException: There is an error in XML document (8, 8). ---> System.InvalidOperationException: There is an error in XML document (8, 8). ---> System.InvalidOperationException: <ArrayOfString xmlns=''> was not expected.
为什么我会收到此异常,我该如何避免?如果可能的话,我想避免只为这个 属性 求助于自定义 XML 序列化。
编辑#1:
我制作了一个运行上述构建和序列化代码的小型控制台程序,然后使用 FileStream
class 将生成的 XML 写入文件。这是它的内容:
<?xml version="1.0"?>
<dictionary>
<item>
<key>
<string>fruits</string>
</key>
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>apple</string>
<string>banana</string>
<string>pear</string>
</ArrayOfString>
</value>
</item>
<item>
<key>
<string>vegetables</string>
</key>
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>carrot</string>
<string>broccoli</string>
<string>cauliflower</string>
<string>onion</string>
</ArrayOfString>
</value>
</item>
<item>
<key>
<string>meats</string>
</key>
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>beef</string>
<string>chicken</string>
</ArrayOfString>
</value>
</item>
</dictionary>
编辑#2:
在我的测试中将 SerializableDictionary<TKey, TValue>
声明的 TValue
类型更改为不同的东西并检查它是否有效后,我可以确认如果 TValue
是 string
,但是如果将它设置为我的自定义可序列化 class,即使 class 装饰有 SerializableAttribute
或实现 IXmlSerializable
接口。所以,问题实际上比字符串数组更大。
在为此苦苦挣扎了几天之后,我得出的结论是,我使用的 Serializable<TKey, TValue>
实现无法处理数组和其他对象。
我求助于在 class 上实现 IXmlSerializable
接口,在 WriteXml
方法中将字典值序列化为列表。在 ReadXml
中,我简单地反序列化存储的列表,然后将其值放回字典中,同时检查列表项上是否没有作为附加 属性 存储的重复键。它很笨重,但它确实有效。
很高兴您找到了解决方案并成功运行。不确定您是否仍然对解决方案持开放态度。尽管如此,您可以按照原样使用 Dictionary
进行以下操作。
尽管 .NET 的 XmlSerializer
不支持字典,但有一些库可以为您完成。一个这样的库是 SharpSerializer (Source, NuGet),它将任何类型序列化为二进制或 XML。
用法 就像:
var serializer = new SharpSerializer();
serializer.Serialize(dict, "test.xml");
输入
var dict = new Dictionary<string, string[]>
{
{ "nikhil", new string[] { "nikhil.0@so.com", "nikhil.1@so.com" } }
};
输出
<Dictionary name="Root" type="System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<Items>
<Item>
<Simple value="nikhil" />
<SingleArray>
<Items>
<Simple value="nikhil.0@so.com" />
<Simple value="nikhil.1@so.com" />
</Items>
</SingleArray>
</Item>
</Items>
</Dictionary>