Search code examples
c#interfacejson.netdeserializationhangfire

Is there some way to have Newtonsoft.Create call your custom creation method on an interface?


I am using Hangfire to run multiple tasks of my custom interface, let's call it IType. However, since Hangfire serializes the method, thereby destroying the instance of that type so when it tries to call it I get an error message like this:

Newtonsoft.Json.JsonSerializationException: Could not create an instance of type IType. Type is an interface or abstract class and cannot be instantiated. Path 'Something', line 1, position 17. at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(...

I figure that one way around this is for each class that is an instance of my interface can store its fully qualified domain name, then I can use reflection to return it the type that it needs, only problem is I don't know how to get Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject() to call my deserialization method. Is there some attribute or some specific method name I need to have in order to make this work?


Solution

  • You need to specify a JsonSerializerSettings that includes a suitable TypeNameHandling value. That will embed the fully qualified name in the JSON in a $type property, which it can then use to deserialize. Here's a complete example:

    using System;
    using Newtonsoft.Json;
    
    interface IFoo
    {
        void Method();
    }
    
    class Foo1 : IFoo
    {
        public string Name { get; set; }
        public void Method() => Console.WriteLine("Method in Foo1");
    }
    
    class Foo2 : IFoo
    {
        public int Value { get; set; }
        public void Method() => Console.WriteLine("Method in Foo2");
    }
    
    class Root
    {
        public IFoo First { get; set; }
        public IFoo Second { get; set; }
    }
    
    class Test
    {
        static void Main()
        {
            Root root = new Root
            { 
                First = new Foo1 { Name = "Fred" },
                Second = new Foo2 { Value = 10 }
            };
            var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
            string json = JsonConvert.SerializeObject(root, settings);
    
            Console.WriteLine(json);
    
            Root root2 = JsonConvert.DeserializeObject<Root>(json, settings);
            root2.First.Method();
            root2.Second.Method();
        }
    }
    

    Output, showing both the JSON and the fact that the interface properties in Root have been suitably deserialized:

    {"$type":"Root, Test","First":{"$type":"Foo1, Test","Name":"Fred"},"Second":{"$type":"Foo2, Test","Value":10}}
    Method in Foo1
    Method in Foo2
    

    There are other TypeNameHandling values you might want to use instead of All - see the documentation for details.