views:

1057

answers:

3

I'm trying to serialize and deserialize a tree of Node objects. My abstract "Node" class as well as other abstract and concrete classes that derive from it are defined in my "Informa" project. In addition, I've created a static class in Informa for serialization / deserialization.

First I'm deconstructing my tree into a flat list of type Dictionary(guid,Node) where guid is the unique id of Node.

I am able to serialize all my Nodes with out a problem. But when I try to deserialize I get the following exception.

Error in line 1 position 227. Element 'http://schemas.microsoft.com/2003/10/Serialization/Arrays:Value' contains data of the 'Informa:Building' data contract. The deserializer has no knowlege of any type that maps to this contract. Add the type corresponding to 'Building' to the list of known types - for example, by usying the KnownTypeAttribute or by adding it to the list of known types passed to DataContract Serializer.

All classes that derive from Node, including Building, have the [KnownType(typeof(type t))] attribute applied to them.

My serialization and deserialization methods are below:

public static void SerializeProject(Project project, string filePath)
{
    try
    {
        Dictionary<Guid, Node> nodeDic = DeconstructProject(project);

        Stream stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);

        //serialize

        DataContractSerializer ser = new DataContractSerializer(typeof(Dictionary<Guid, Node>),"InformaProject","Informa");

        ser.WriteObject(stream,nodeDic);

        // Cleanup
        stream.Close();
    }
    catch (Exception e)
    {
        MessageBox.Show("There was a problem serializing " + Path.GetFileName(filePath) + ". \n\nException:" + e.Message, "Doh!", MessageBoxButtons.OK, MessageBoxIcon.Error);
        throw e;
    }

}



public static Project DeSerializeProject(string filePath)
{
    try
    {
        Project proj;

        // Read the file back into a stream
        Stream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);

        DataContractSerializer ser = new DataContractSerializer(typeof(Dictionary<Guid, Node>), "InformaProject", "Informa");

        Dictionary<Guid, Node> nodeDic = (Dictionary<Guid, Node>)ser.ReadObject(stream);

        proj = ReconstructProject(nodeDic);        

        // Cleanup
        stream.Close();

        return proj;

    }
    catch (Exception e)
    {
        MessageBox.Show("There was a problem deserializing " + Path.GetFileName(filePath) + ". \n\nException:" + e.Message, "Doh!", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return null;
    }

}
A: 

All classes that derive from Node, including Building, have the [KnownType(typeof(type t))] attribute applied to them.

KnownType is usually applied to the base type - i.e.

[DataContract, KnownType(typeof(Building)), ...]
abstract class Node { ... }

(note - you can also specify the known-types in the DataContractSerializer constructor, without requiring attributes)

EDIT RE YOUR REPLY

If the framwork class doesn't know about all the derived types, then you need to specify the known types when creating the serializer:

[DataContract] abstract class SomeBase { }
[DataContract] class Foo : SomeBase { }
[DataContract] class Bar : SomeBase { }
...
// here the knownTypes argument is important
new DataContractSerializer(typeof(SomeBase),
      new Type[] { typeof(Foo), typeof(Bar) });

This can be combined with (for example) preserveObjectReferences etc by replacing the null in the previous example.

END EDIT

However, without something reproducible (i.e. Node and Building), it is going to be hard to help much.

The other odd thing; trees structures are very well suited to things like DataContractSerializer - there is usually no need to flatten them first, since trees can be trivially expressed in xml. Do you really need to flatten it?


Example:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

[DataContract, KnownType(typeof(Building))]
abstract class Node {
    [DataMember]
    public int Foo {get;set;}
}
[DataContract]
class Building : Node {
    [DataMember]
    public string Bar {get;set;}
}

static class Program
{
    static void Main()
    {
        Dictionary<Guid, Node> data = new Dictionary<Guid, Node>();
        Type type = typeof(Dictionary<Guid, Node>);
        data.Add(Guid.NewGuid(), new Building { Foo = 1, Bar = "a" });
        StringWriter sw = new StringWriter();
        using (XmlWriter xw = XmlWriter.Create(sw))
        {
            DataContractSerializer dcs = new DataContractSerializer(type);
            dcs.WriteObject(xw, data);
        }

        string xml = sw.ToString();

        StringReader sr = new StringReader(xml);
        using (XmlReader xr = XmlReader.Create(sr))
        {
            DataContractSerializer dcs = new DataContractSerializer(type);
            Dictionary<Guid, Node> clone = (Dictionary<Guid, Node>)
                dcs.ReadObject(xr);
            foreach (KeyValuePair<Guid, Node> pair in clone)
            {
                Console.WriteLine(pair.Key + ": " + pair.Value.Foo + "/" +
                    ((Building)pair.Value).Bar);
            }
        }
    }
}
Marc Gravell
My tree structure allows Nodes to have multiple parents. Which complicates serialization, because I don't want to create duplicate instances.
Eric Anastas
I'm trying to define the information about my tree structure as well as methods to serialize it in one "framework" project, and then actual implementations of it in other projects.
Eric Anastas
So my framework project doesn't know all the types which might derive from Node. However Building is in my framework project, so I don't think that is the problem yet.
Eric Anastas
A: 

What is the difference between these two uses of the KnownTypes attribute? I didn't realize that you could/would want to specify that one class is a KnownType of another.

[DataContract]
[KnownType(typeof(Building))]
abstract class Node {
    [DataMember]
    public int Foo {get;set;}
}
[DataContract]
class Building : Node {
    [DataMember]
    public string Bar {get;set;}
}


[DataContract] 
[KnownType(typeof(Node))]
abstract class Node {
    [DataMember]
    public int Foo {get;set;}
}
[KnownType(typeof(Building))]
class Building : Node {
    [DataMember]
    public string Bar {get;set;}
}
Eric Anastas
No... [KnownType] relates to *inheritance*; the serializer needs to know that it should expect Building instances (among others) when handling Node. I'll edit my answer to clarify.
Marc Gravell
+1  A: 
Eric Anastas