-1

I want to create some immutable classes with Json.net, but encountered error message: "Could not create an instance of type SolutionName.InterfaceClassName.Type is an interface or abstract class and cannot be instantiated".

The problem:

I have an immutable class with some interface implemented.

public interface ISubClass1
    {
        int number { get; }
        string string1 { get; }
    }

Subclass:

public class SubClass1 : ISubClass1
    {
        public int number { get; }
        public string string1 { get; }  

        public SubClass1(int a, string str1)
        {
            number = a;
            string1 = str1;
        }
    }

public class SubClass2 : ISubClass1
        {
            public int number { get; }
            public string string1 { get; }  
            public string string2 { get; }  

            public SubClass2(int a, string str1, string str2)
            {
                number = a;
                string1 = str1;
                string2 = str2;
            }
        }

Implementation:

class Class1
    {
        public int SomeInt{ get; }
        public ISubClass1 SomeInterface { get; }

        public Class1(int a, ISubClass1 subclass)
        {
            SomeInt= a;
            SomeInterface = subclass;
        }
    }

I can serialize this object, everything works. But an error will occur during deserialization

"Could not create an instance of type SolutionName.InterfaceClassName.Type is an interface or abstract class and cannot be instantiated"

This is due to ISubClass1 subclass can not be recognized by Json.net.

Middle
  • 143
  • 1
  • 8
  • There is no question here. – InBetween Jul 01 '20 at 14:03
  • @InBetween I could make this into Q&A format, please read the first paragraph, and is this how you treat people who share knowledge? – Middle Jul 01 '20 at 14:04
  • If you know the answer, why ask the question? – InBetween Jul 01 '20 at 14:05
  • @InBetween Because I searched for a solution for 1 week, and there was none here. So I wanted to document this process to help others after I solved this issue? – Middle Jul 01 '20 at 14:07
  • Yes, your intention is very laudable but this site doesn't work this way. If there is no match in SO its either due to not searching well, its not a common issue or people generally know already how to solve it. – InBetween Jul 01 '20 at 14:09
  • 1
    @Middle don't post the answer in the question. Answer your own question! And mark it as the correct answer. It's something valid. – DrkDeveloper Jul 01 '20 at 14:09
  • @DrkDeveloper I can definitely do that. – Middle Jul 01 '20 at 14:10
  • @InBetween This assumption is very flawed. I am an example of someone seeking answer and couldn't get it, and I didn't have to waste a week's time if post like this is present. – Middle Jul 01 '20 at 14:11
  • Related or duplicate: [How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?](https://stackoverflow.com/q/8030538/3744182), [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182), [Private setters in Json.Net](https://stackoverflow.com/q/4066947/3744182), [Json.net `JsonConstructor` constructor parameter names](https://stackoverflow.com/q/43032552/3744182). – dbc Jul 01 '20 at 21:05
  • @dbc Related yes, please refer to solution step 3 for the answers not present in all the above posts. – Middle Jul 01 '20 at 21:54

1 Answers1

1

The solution:

You will need custom Json converter so that you can resolve ISubclass1 with the concrete types you desire.

1 - Add this generic converter to your solution.

    public abstract class JsonCreationConverter<T> : JsonConverter
    {
        /// <summary>
        /// Create an instance of objectType, based properties in the JSON object
        /// </summary>
        /// <param name="objectType">type of object expected</param>
        /// <param name="jObject">
        /// contents of JSON object that will be deserialized
        /// </param>
        protected abstract T Create(Type objectType, JObject jObject);
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        public override object ReadJson(JsonReader reader,
                                        Type objectType,
                                         object existingValue,
                                         JsonSerializer serializer)
        {
            // Load JObject from stream
            JObject jObject = JObject.Load(reader);
    
            // Create target object based on JObject
            T target = Create(objectType, jObject);
    
            // Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);
    
            return target;
        }
    }

2 - Then inherit any interface you want to translate like the example below. Take note that FieldExists check is checking for a field that would identify the type of subclass. This way Json do not need $type information in order to deserialize correctly. Reference: BulletProof Deserialization

    public class SubClass1Converter : JsonCreationConverter<ISubClass1>
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new InvalidOperationException("Use default serialization.");
        }
    
        protected override ISubClass1 Create(Type objectType, JObject jObject)
        {
            if (FieldExists("string2", jObject))
            {
                return new SubClass2();
            }
            else
            {
                return new SubClass1();
            }
        }
    
        private bool FieldExists(string fieldName, JObject jObject)
        {
            return jObject[fieldName] != null;
        }
    }

3 - You have modify your subclasses and implementations with Json.net Attributes, else deserialization will default values to null. You have an immutable class with some interface implemented.

Some keys points:

  1. [JsonProperty] needs to be present on every property, else the properties will be read as null;
  2. [public SubClass1() { }] an empty constructor needs to be present for converters declare an empty class.
  3. `[JsonConstructor] ' needs to be marked on the constructor that allow properties to get values passed to them in the immutable objects.
  4. { get; } needs to be changed to { get; private set; } else all properties will set to null
  5. public ISubClass1 SomeInterface { get; private set;} in the implementation needs to be marked with [JsonConverter(typeof(SubClass1Converter))] attribute.

Subclass:

    public class SubClass1 : ISubClass1
        {
            [JsonProperty] 
            public int number { get; private set;}
            [JsonProperty]
            public string string1 { get; private set;} 

            public SubClass1() { }

            [JsonConstructor]
            public SubClass1(int a, string str1)
            {
                number = a;
                string1 = str1;
            }
        }

    public class SubClass2 : ISubClass1
            {
                [JsonProperty] 
                public int number { get; private set;}
                [JsonProperty]
                public string string1 { get; private set;} 
                [JsonProperty]
                public string string2 { get; private set;}  

                public SubClass2() { }

                [JsonConstructor]
                public SubClass2(int a, string str1, string str2)
                {
                    number = a;
                    string1 = str1;
                    string2 = str2;
                }
            }

Implementation:

class Class1
    {
        [JsonProperty] 
        public int SomeInt{ get; private set;}
        [JsonProperty]
        [JsonConverter(typeof(SubClass1Converter))]
        public ISubClass1 SomeInterface { get; private set;}

        [JsonConstructor] 
        public Class1(int a, ISubClass1 subclass)
        {
            SomeInt= a;
            SomeInterface = subclass;
        }
    }

Usage:

JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.Formatting = Formatting.Indented;
jsonSettings.Converters.Add(new SubClass1Converter()); //Optional
jsonSettings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; //Optional

string jsonStr = JsonConvert.SerializeObject(cls1, jsonSettings);

Class1 deserializedObject = JsonConvert.DeserializeObject<Class1>(jsonStr , jsonSettings);

References:

Middle
  • 143
  • 1
  • 8