1

I am using a variation of the type object pattern (basically smart enums). Since this problem can best be explained with code I will jump right into it.

    class Program
    { 
        static void Main(string[] args)
        {
            Test C = Test.B;
            Console.WriteLine(C == Test.B); //Returns true

            string Json = JsonConvert.SerializeObject(C);

            C = JsonConvert.DeserializeObject<Test>(Json);
            Console.WriteLine(C == Test.B); //Returns false
        }
    }

    public class Test
    {
        public int A { get; set; }

        public Test(int A)
        {
            this.A = A;
        }

        public static Test B = new Test(100);
    }

In this example Test is the type object, and instances of it are assigned to it's static field, B. In real life scenarios there would be multiple of these static fields, each representing a different type. When I serialize and deserialize, the test object is serialized purely as data. I understand why this is happening, but I don't know what to do about it. I would like to somehow preserve instances of Test being references to a static member in that class.

Pema Malling
  • 131
  • 8

4 Answers4

1

What you are looking for is support for the IObjectReference interface:

Implement this interface on objects that are references to a different object, which cannot be resolved until the current object is completely restored. During the fixup stage, any object implementing IObjectReference is queried for its real object and that object is inserted into the graph.

Unfortunately, Json.NET does not support this interface. However, it turns out to be quite easy to extend Json.NET to support this interface in cases where the type in question also implements ISerializable. This is a quite reasonable restriction given that, in practice, these two interfaces are often used together, as is shown in the documentation example.

First, introduce the following custom contract resolver:

public class ISerializableRealObjectContractResolver : DefaultContractResolver
{
    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    static ISerializableRealObjectContractResolver instance;

    static ISerializableRealObjectContractResolver() { instance = new ISerializableRealObjectContractResolver(); }

    public static ISerializableRealObjectContractResolver Instance { get { return instance; } }

    public ISerializableRealObjectContractResolver()
        : base()
    {
        this.IgnoreSerializableInterface = false;
    }

    protected override JsonISerializableContract CreateISerializableContract(Type objectType)
    {
        var contract = base.CreateISerializableContract(objectType);

        var constructor = contract.ISerializableCreator;
        contract.ISerializableCreator = args => 
        {
            var obj = constructor(args);
            if (obj is IObjectReference)
            {
                var context = (StreamingContext)args[1];
                obj = ((IObjectReference)obj).GetRealObject(context);
            }
            return obj;
        };
        return contract;
    }
}

Now, modify your psuedo-enum Test type to implement ISerializable and IObjectReference:

public class Test : ISerializable, IObjectReference
{
    readonly int a;

    public int A { get { return a; } }

    public Test(int A)
    {
        this.a = A;
    }

    public static readonly Test B = new Test(100);

    #region ISerializable Members

    protected Test(SerializationInfo info, StreamingContext context)
    {
        a = info.GetInt32("A");
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("A", A);
    }

    #endregion

    #region IObjectReference Members

    public object GetRealObject(StreamingContext context)
    {
        // Check all static properties to see whether the key value "A" matches.  If so, return the static instance.
        if (this.A == B.A)
            return B;
        return this;
    }

    #endregion
}

I also made the type immutable since that is clearly the requirement here.

Now your unit test will pass when using this contract resolver:

Test C = Test.B;
Console.WriteLine(C == Test.B); //Returns true

string Json = JsonConvert.SerializeObject(C, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(Json);

C = JsonConvert.DeserializeObject<Test>(Json, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(C == Test.B); //Still returns true      

if (!object.ReferenceEquals(C, Test.B))
{
    throw new InvalidOperationException("!object.ReferenceEquals(C, Test.B)");
}
else
{
    Console.WriteLine("Global singleton instance deserialized successfully."); 
}

Note however that Json.NET only supports the ISerializable interface in full trust.

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

Not possible by default, since the JSON deserializer doesn't care about existing references or static objects in your class.

You could compare for equality using a custom Equals method, but I guess that isn't what you want.

eavidan
  • 5,324
  • 1
  • 13
  • 16
0

Don't serialize MyObj.Test, suppress that with an Ignore attribute. Instead, expose a property MyObj.TestID that returns MyObj.Test.ID. When TestID is set on MyObj, load the Test from a static collection keyed by the ID and set MyObj.Test to that value.

Pxtl
  • 880
  • 8
  • 18
0

First of all, Type Object patterns are supposed to be used when you don't want to go through the inheritance hierarchy every time you define a new derivative of a base class. Having a type object attached as static didn't make sens to be at the first place to be honest. As you mentioned it's a variation Im not going to jump on that.

Looks like you want to be able to keep the reference even after deserialization using json.net.

Now if you want to do that you might want to have a look here.

Taking snippets from the aforementioned link as it's better to have a sample here as this is a StackOverflow answer. It should sustain even the provided link is dead.

Your first option is to use default PreserveReferencesHandling. The associated sample is following where you can reference same objects in a list and point to it. I don't think it actually keeps the old reference but sure helps when you have same things in a list and you dont want to go with your own IEqualityComparer or IEquatable implementations:

string json = JsonConvert.SerializeObject(people, Formatting.Indented,
    new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });

//[
//  {
//    "$id": "1",
//    "Name": "James",
//    "BirthDate": "1983-03-08T00:00Z",
//    "LastModified": "2012-03-21T05:40Z"
//  },
//  {
//    "$ref": "1"
//  }
//]

List<Person> deserializedPeople = JsonConvert.DeserializeObject<List<Person>>(json,
    new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });

Console.WriteLine(deserializedPeople.Count);
// 2

Person p1 = deserializedPeople[0];
Person p2 = deserializedPeople[1];

Console.WriteLine(p1.Name);
// James
Console.WriteLine(p2.Name);
// James

bool equal = Object.ReferenceEquals(p1, p2);
// true

You can use IsReference attribute to control which properties would be kept as references:

[JsonObject(IsReference = true)]
public class EmployeeReference
{
    public string Name { get; set; }
    public EmployeeReference Manager { get; set; }
}

Now if you want to keep the exact same reference in the code for yourself (I don't think this is really a good design anyway, you might just need a Equality comparison method and be done with it), you need a custom IReferenceResolver defined here.

Furthermore, if you want to have something like that, look no further than Json.net's source code here.

It's an IdReferenceResolver that you can possibly use to preserve your object reference as Guid and possibly use it your way.

And if you want to know how DefaultReferenceResolver works you can have a look at this stackoverflow thread.

Community
  • 1
  • 1
Swagata Prateek
  • 1,076
  • 7
  • 15