问题
I have a class with a dictionary attribute I want to serialize. I've read that serializing a dictionary isn't directly possible so I serialize/deserialize a list I then transform to a dictionary. It works, but i would like to know if there is a proper way to do this.
[Serializable]
public class Album
{
private List<Photo> photos = new List<Photo>();
[XmlArray]
public List<Photo> Photos
{
get { return photos; }
set
{
photos = value;
}
}
private Dictionary<string, Photo> dicoPhotos = new Dictionary<string, Photo>();
[XmlIgnore]
public Dictionary<string, Photo> DicoPhotos
{
get { return dicoPhotos; }
set { dicoPhotos = value; }
}
public void fillPhotosDictionnary()
{
this.dicoPhotos = this.photos.ToDictionary(p => p.Nom, p => p);
}
}
I've tried to fill the dictionary in the Photo's setter, but it doesn't work and I can't figure out why.
回答1:
I'd suggest leveraging the DataContractSerializer and its associated attributes (DataContract
and DataMember
). Its available in .NET 3.0+.
Its very similar to the XmlSerializer
, but I've found that its much better at general serialization. Also, it supports serialization of Dictionary<TKey, TValue>
out of the box.
In your case it should be a simple enough matter to switch some of your attributes to the appropriate DataContract ones, and then use a DataContractSerializer instead of an XmlSerializer.
In the end the output will be basically the same (an XML document), and the code required in your class is a lot cleaner.
[DataContract(Name = "Album", Namespace = "DataContracts")]
public class Album
{
[DataMember(Name = "DicoPhotos")]
private Dictionary<string, Photo> dicoPhotos = new Dictionary<string, Photo>();
public Dictionary<string, Photo> DicoPhotos
{
get { return dicoPhotos; }
set { dicoPhotos = value; }
}
}
There are some tricks and traps around the DataContractSerializer:
- Make sure it knows about the list of types you are serializing.
- Make sure that everything has an appropriate name and namespace (to protect yourself against property name and namespace changes).
If you're only serializing for non-persistent purposes (i.e. across the wire transfers), consider using the NetDataContractSerializer. Do NOT use this if you are persisting to any sort of permanent construct (like disk/database), or you'll probably have a serious headache later (due to the way it serializes).
回答2:
Try to use Protobuf-net. Then the code will be similar to the following:
[Serializable]
[ProtoContract]
public class Album
{
private List<Photo> photos = new List<Photo>();
[ProtoMember(1)]
public List<Photo> Photos
{
get { return photos; }
set
{
photos = value;
}
}
private Dictionary<string, Photo> dicoPhotos = new Dictionary<string, Photo>();
[ProtoMember(2)]
public Dictionary<string, Photo> DicoPhotos
{
get { return dicoPhotos; }
set { dicoPhotos = value; }
}
}
And Serialization Method:
public void Serialize(Object obj, String FileFullPath)
{
byte[] serialized;
using (var ms = new MemoryStream())
{
Serializer.Serialize(ms, obj);
serialized = ms.ToArray();
}
File.WriteAllBytes(FileFullPath, serialized);
}
回答3:
It doesn't work, because XmlSerializer
stores items in the list one-by-one, so the setter is not necessarily called during deserialization. There are several options what you can do:
- build a wrapper class implementing
IList
and routing calls to the underlying dictionary; or - implement
IXmlSerializable
and do the (de)serialization of the whole class yourself; or - call a “fix-up” method (like your's
fillPhotosDictionnary
) after the deserialization.
Personally, I'd choose option (1) because Dictionary
serialization is a common problem and you may end up needing it in other classes, too.
回答4:
I realize this question is a bit old but I didn't really like the other solutions I found because they format the xml in a different way than I wanted. So I did it this way.
I made a custom dynamic type based on the dictionary keys, create an instance of this new type, then serialize that object.
public void Serialize(string OutPath, Dictionary<string, object> Input) {
//Define stuff
AssemblyName assName = new AssemblyName("CustomType");
AssemblyBuilder assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder modBuilder = assBuilder.DefineDynamicModule(assName.Name);
TypeBuilder typBuilder = modBuilder.DefineType("NewType", TypeAttributes.Public);
//Add all the keys as fields
foreach (string Key in Input.Keys) {
typBuilder.DefineField(Key, Input[Key].GetType(), FieldAttributes.Public);
}
//Make the new type
Type newType = typBuilder.CreateType();
//make instance
object newInstance = Activator.CreateInstance(newType);
//set Values
foreach (string Key in Input.Keys) {
newInstance.GetType().GetField(Key).SetValue(newInstance, Input[Key]);
}
//serialize XML
XmlSerializer xs = new XmlSerializer(newType);
TextWriter tw = new StreamWriter(OutPath);
xs.Serialize(tw, newInstance);
tw.Flush();
tw.Close();
}
If you want to include a dictionary with primitive fields from another type, you can just include the object in the parameters to pass the values and add some stuff like this to the proper locations:
//This part adds the fields to the custom type, include near where the dictionary keys are being added as fields
foreach (FieldInfo FI in InputObject.GetType().GetFields()) {
if (FI.FieldType.IsPrimitive || FI.FieldType == typeof(string)) {
FieldInfo fi = newInstance.GetType().GetField(FI.Name);
fi.SetValue(newInstance, FI.GetValue(InputObject));
}
}
//This part adds the values to the object, include near where the values are being set
foreach (FieldInfo FI in InputObject.GetType().GetFields()) {
if (FI.FieldType.IsPrimitive || FI.FieldType == typeof(string)) {
FieldBuilder fieBuilder = typBuilder.DefineField(FI.Name, FI.FieldType, FieldAttributes.Public);
}
}
This code:
//build dictionary
Dictionary<string, object> Input = new Dictionary<string, object>();
Input.Add("Value1", "One");
Input.Add("Value2", "Two");
Input.Add("Value3", 3);
Input.Add("Date", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
//Serialize!
Serialize(FilePath, Input);
Builds an xml output like this:
<?xml version="1.0" encoding="utf-8" ?>
- <NewType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Value1>One</Value1>
<Value2>Two</Value2>
<Value3>3</Value3>
<Date>2014-08-28 18:03:58</Date>
</NewType>
Then you can read in/deserialize the xml like this:
public Dictionary<string, object> Deserialize(string OutPath) {
Dictionary<string, object> Output = new Dictionary<string, object>();
//create the xmlDocument
XmlDocument xd = new XmlDocument();
xd.Load(XmlReader.Create(OutPath));
//Scan all the nodes in the main doc and add them to the dictionary
//you can recursively check child nodes if your document requires.
foreach (XmlNode node in xd.DocumentElement) {
Output.Add(node.Name, node.InnerText);
}
return Output;
}
来源:https://stackoverflow.com/questions/20787286/dictionary-xml-serialization