0

I am using a DataContractJsonSerializer to serialize an object graph. When I construct the objects, each receives a reference to an instance of a utility object (it's a factory, for creating instances of subclasses of an abstract contract class) - which works great until the graph is serialized and then deserialized again, whereupon the objects no longer have a reference to the utility object any more. I need this reference. How would you recommend I implement this (singletons don't work because separate graphs need their own instance of the object)?

Void Star
  • 2,401
  • 4
  • 32
  • 57

2 Answers2

0

One way to accomplish this is with a data contract surrogate. Using surrogates, you can replace your "real" factory with a dummy stub during serialization. Then, during deserialization, replace the dummy with the desired factory.

Thus, if your classes look something like:

public abstract class FactoryBase
{
}

public class Factory : FactoryBase
{
}

public interface IHasFactory
{
    FactoryBase Factory { get; }
}

[DataContract]
public abstract class HasFactoryBase : IHasFactory
{
    [DataMember(IsRequired = true)]
    FactoryBase factory;

    public FactoryBase Factory { get { return factory; } }

    public HasFactoryBase(FactoryBase factory)
    {
        this.factory = factory;
    }
}

[DataContract]
public class Foo : HasFactoryBase
{
    public Foo(FactoryBase factory)
        : base(factory)
    {
        this.Bars = new List<Bar>();
    }

    [DataMember]
    public List<Bar> Bars { get; set; }
}

[DataContract]
public class Bar : HasFactoryBase
{
    public Bar(FactoryBase factory) : base(factory) { }
}

You define an IDataContractSurrogate to replace all occurrences of FactoryBase with a surrogate as follows:

public class FactorySurrogateSelector : IDataContractSurrogate
{
    [DataContract]
    class FactorySurrogate
    {
    }

    readonly FactoryBase factory;

    public FactorySurrogateSelector(FactoryBase factory)
    {
        this.factory = factory;
    }

    #region IDataContractSurrogate Members

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public Type GetDataContractType(Type type)
    {
        if (typeof(FactoryBase).IsAssignableFrom(type))
            return typeof(FactorySurrogate);
        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        if (obj is FactorySurrogate)
            return factory;
        return obj;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
        throw new NotImplementedException();
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if (obj is FactoryBase)
        {
            return new FactorySurrogate();
        }
        return obj;
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        throw new NotImplementedException();
    }

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Then, serialize and deserialize as follows:

var factory = new Factory();
var test = new Foo(factory)
{
    Bars = { new Bar(factory) },
};

var surrogate = new FactorySurrogateSelector(factory);
var serializer = new DataContractJsonSerializer(test.GetType(), Enumerable.Empty<Type>(), int.MaxValue, false, surrogate, false);

byte[] json;
using (var stream = new MemoryStream())
{
    serializer.WriteObject(stream, test);
    json = stream.ToArray();
}

Foo test2;
using (var stream = new MemoryStream(json))
{
    test2 = (Foo)serializer.ReadObject(stream);
}

if (test2.Factory != test.Factory)
    throw new InvalidOperationException();

Notice that the desired factory was passed directly into the constructor of the FactorySurrogateSelector, then eventually set inside each type that contains instances of the factory type.

The resulting JSON will look like:

{
  "factory": {},
  "Bars": [
    {
      "factory": {}
    }
  ]
}

Some qualifications:

  • Your factory must inherit from some common base class, here FactoryBase. The data contract serializers will never serialize an interface member, e.g. IFactory factory where IFactory is your factory interface, even when there is an applicable surrogate.

  • The empty "factory": {} objects must appear in the JSON in order for the surrogate to inject the correct "real" factory value during deserialization. Hence the [DataMember(IsRequired = true)].

dbc
  • 104,963
  • 20
  • 228
  • 340
0

Another way to accomplish is to introduce a thread static or thread local factory object, then populate your classes with it using an [OnDeserializing] callback.

Thus, if you define your types as follows:

public interface IFactory
{
}

public class Factory : IFactory
{
}

public interface IHasFactory
{
    IFactory Factory { get; }
}

[DataContract]
public abstract class HasFactoryBase : IHasFactory
{
    [ThreadStatic]
    static IFactory deserializedFactory;

    static IFactory DeserializedFactory
    {
        get
        {
            return deserializedFactory;
        }
        set
        {
            deserializedFactory = value;
        }
    }

    public static IDisposable SetDeserializedFactory(IFactory factory)
    {
        return new PushValue<IFactory>(factory, () => DeserializedFactory, val => DeserializedFactory = val);
    }

    IFactory factory;

    public IFactory Factory { get { return factory; } }

    public HasFactoryBase(IFactory factory)
    {
        this.factory = factory;
    }

    [OnDeserializing]
    void OnDeserializing(StreamingContext context)
    {
        this.factory = DeserializedFactory;
    }
}

public struct PushValue<T> : IDisposable
{
    Action<T> setValue;
    T oldValue;

    public PushValue(T value, Func<T> getValue, Action<T> setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.setValue = setValue;
        this.oldValue = getValue();
        setValue(value);
    }

    #region IDisposable Members

    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
    public void Dispose()
    {
        if (setValue != null)
            setValue(oldValue);
    }

    #endregion
}

[DataContract]
public class Foo : HasFactoryBase
{
    public Foo(IFactory factory)
        : base(factory)
    {
        this.Bars = new List<Bar>();
    }

    [DataMember]
    public List<Bar> Bars { get; set; }
}

[DataContract]
public class Bar : HasFactoryBase
{
    public Bar(IFactory factory) : base(factory) { }
}

You can serialize and deserialize as follows:

var factory = new Factory();
var test = new Foo(factory)
{
    Bars = { new Bar(factory) },
};

var serializer = new DataContractJsonSerializer(test.GetType());

byte [] json;
using (var stream = new MemoryStream())
{
    serializer.WriteObject(stream, test);
    json = stream.ToArray();
}

Foo test2;
using (HasFactoryBase.SetDeserializedFactory(factory))
using (var stream = new MemoryStream(json))
{
    test2 = (Foo)serializer.ReadObject(stream);
}

if (test2.Factory != test.Factory)
    throw new InvalidOperationException();

And the JSON will look like:

{
  "Bars": [
    {}
  ]
}

Some notes:

  • The factory object does not appear at all in the JSON.

  • The factory objects no longer need to inherit from some abstract base class, they can simply implement a common IFactory interface.

dbc
  • 104,963
  • 20
  • 228
  • 340