4

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.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
thibon
  • 360
  • 2
  • 7
  • 19
  • 2
    possible duplicate of [Why isn't there an XML-serializable dictionary in .NET?](http://stackoverflow.com/questions/1124597/why-isnt-there-an-xml-serializable-dictionary-in-net) – John Saunders Dec 26 '13 at 15:42

4 Answers4

3

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

Todd Bowles
  • 1,554
  • 15
  • 24
1

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

        }
GhostCKY
  • 764
  • 10
  • 19
  • -1: pretty radical change just to get a dictionary serialized. – John Saunders Dec 27 '13 at 06:29
  • Since it is quite convenient to operate and fits under almost all data types. – GhostCKY Dec 27 '13 at 06:49
  • Yeah, but to work around a single problem you'll have the OP change the serialization technology he's using. And to change to one he's probably never used before; possibly one that's never been used at his entire company. Just for a dictionary. – John Saunders Dec 27 '13 at 06:51
  • 1
    I agree that corporate purposes, this method is not suitable, but for personal use, it is always interesting to learn something new. – GhostCKY Dec 27 '13 at 07:02
0

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:

  1. build a wrapper class implementing IList and routing calls to the underlying dictionary; or
  2. implement IXmlSerializable and do the (de)serialization of the whole class yourself; or
  3. 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.

Ondrej Tucny
  • 27,626
  • 6
  • 70
  • 90
0

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;
    }
182764125216
  • 931
  • 14
  • 38