Can I deserialize generics without a reference to the type?
Asked Answered
T

2

10

I am attempting to save/load a class to an xml file that contains generic types using a DataContractSerializer. I have the save working, but have realized I can't load it because I don't have the list of knownTypes for the deserializer.

Is there a way of serializing/deserializing this class that would allow me to deserialize it without referencing any of the stored types directly?

Here is my SessionVariables class that I am trying to save/load:

[DataContract]
public class SessionVariables
{
    [DataMember]
    private Dictionary<Type, ISessionVariables> _sessionVariables = new Dictionary<Type, ISessionVariables>();
    private object _syncLock = new object();

    public T Get<T>()
        where T : ISessionVariables, new()
    {
        lock (_syncLock)
        {
            ISessionVariables vars = null;

            if (_sessionVariables.TryGetValue(typeof(T), out vars))
                return (T)vars;

            vars = new T();
            _sessionVariables.Add(typeof(T), vars);

            return (T)vars;
        }
    }

    public IList<Type> GetKnownTypes()
    { 
        IList<Type> knownTypes = new List<Type>();

        knownTypes.Add(this.GetType().GetType()); // adds System.RuntimeType

        foreach (Type t in _sessionVariables.Keys)
        {
            if (!knownTypes.Contains(t))
                knownTypes.Add(t);
        }

        return knownTypes;
    }
}

The different modules of the application extend the ISessionVariables interface to create their own set of session variables, like this:

[DataContract]
public class ModuleASessionVariables : ISessionVariables
{
    [DataMember]
    public string ModuleA_Property1{ get; set; }
    [DataMember]
    public string ModuleA_Property2 { get; set; }
}


[DataContract]
public class ModuleBSessionVariables : ISessionVariables
{
    [DataMember]
    public string ModuleB_Property1{ get; set; }
    [DataMember]
    public string ModuleB_Property2 { get; set; }
}

And a singleton instance of the SessionVariables class is used to access session variables, like this:

singletonSessionVariables.Get<ModuleASessionVariables>().ModuleA_Property1
singletonSessionVariables.Get<ModuleBSessionVariables>().ModuleB_Property2

I got the save working like this:

using (FileStream writer = new FileStream(@"C:\test.txt", FileMode.Create))
{
    DataContractSerializer dcs = new DataContractSerializer(typeof(SessionVariables), singletonSessionVariables.GetKnownTypes());
    dcs.WriteObject(writer, singletonSessionVariables);
    writer.Close();
}

However this method does not work to deserialize the class because I don't know it's known types.

Can I serialize and deserialize generic types when I don't have direct library references to any of the types used? And if so, how?

Truda answered 27/11, 2013 at 14:34 Comment(5)
:/ I'd use the NetDataContractSerializer, which includes type information in the result.Burnard
Don't think it's possible: how are we going to create an instance of a type, if type itself is unknown ?Tice
@Will Thanks, I wasn't aware that existed before! You should write up an answer for this :)Truda
@Will I think so, I was successfully able to serialize and deserialize the SessionVariables object and child objects to a file without specifying the types in advance, and the types and data came out successfully. The only minor issue I had was that the _syncObject gets initialized as null when deserializing. I still need to test with moving the child objects to separate assemblies though, and making sure it can deserialize with no reference at all.Truda
I don't think that's going to work when you serialize and deserialize data using the NetDataContractSerializer it is to and from a concrete type. Perhaps you might consider using JSON.NET, and deserialize the json using dynamic. weblog.west-wind.com/posts/2012/Aug/30/…Greathearted
B
7

The problem here is that you aren't just wanting to serialize data, but you also want to serialize data about your data, i.e., (cue the dramatic chipmunk) metadata.

That metadata, in this case, are the types of the models that held the data originally. Normally, this isn't an issue, but as you've discovered if you're taking advantage of polymorphism in your design, your single collection may contain two or more different types, each of which needs to be deserialized to their original type.

This is usually accomplished by saving this Type metadata to the serialized result. Different serialization methods does this in different ways. Xaml serialization uses xml namespaces associated with .net namespaces, then names the elements after the original type name. Json.net accomplishes this via a specific named value saved to the json object.

The default DataContractSerializer is not Type aware. Therefore you need to replace it with a version that understands the .NET Type system and can serialize/deserialize Type metadata to the resulting xml. Luckily, one already exists in the framework, the NetDataContractSerializer.

And that's how you pad a link-only answer. The Aristocrats.

Burnard answered 2/12, 2013 at 20:34 Comment(1)
This worked out great, with no reference to the external module libraries needed. The only change I had to make is to mark the _syncLock obejct with a [DataMember] attribute or it deserializes as null. Am looking for a way around that now. Thanks :)Truda
E
0

You could accomplish this using a custom DataContractResolver. This allows you to plug into the deserialization pipeline and provide a type to deserialize into based upon the type/namespace that is found in the serialized graph.

Here's a good article on it: http://blogs.msdn.com/b/carlosfigueira/archive/2011/09/21/wcf-extensibility-data-contract-resolver.aspx

IDesign has an implementation of a resolver that can be used for dynamic discovery of types on their site: http://idesign.net/Downloads/GetDownload/1848 (you will probably have to make some modifications to handle generics)

Elimination answered 9/12, 2013 at 15:24 Comment(1)
Note for you: This solution requires .net 4-- I just noticed the .net3-5 tag in your question.Elimination

© 2022 - 2024 — McMap. All rights reserved.