0

When trying to deserialize Json I cannot figure a way around the error:

'Could not create an instance of type ConsoleApp1.IDisplayInstructions. Type is an interface or abstract class and cannot be instantiated. Path 'displayInstructions.AGB', line 4, position 34.'

I understand the meaning behind it; I need to instruct the Json deserializer which concrete class to use for the interface members. I just don't know how to do it. I tried using a JsonConstructor attribute, or use a custom deserializer - but I was not able to get either method to work.

There is another question that is similar (JSON.NET - how to deserialize collection of interface-instances?), but this is a field that is an interface, not the class itself.

using Newtonsoft.Json;
using System.Collections.Generic;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string jsonData = @"{
    'Term' : 'john'
   ,'resourceTypes' : ['POL', 'CLM', 'WRK']
   ,'displayInstructions': {'AGB':{'DisplayAttributes':['AssuredName','PolicyNumber','DistributorName','EffectiveDate'],'Format':'|resource_type| (|rank|) {0} / {1}'}
            ,'AGT':{'DisplayAttributes':['AssuredName','PolicyNumber','DistributorName','EffectiveDate'],'Format':'|resource_type| (|rank|) {0} / {1}'}
            ,'AGY':{'DisplayAttributes':['AssuredName','PolicyNumber','DistributorName','EffectiveDate'],'Format':'|resource_type| (|rank|) {0} / {1}'}
            ,'CLM':{'DisplayAttributes':['AssuredName','PolicyNumber','DistributorName','EffectiveDate'],'Format':'|resource_type| (|rank|) {0} / {1}'}
            ,'PLU':{'DisplayAttributes':['AssuredName','PolicyNumber','DistributorName','EffectiveDate'],'Format':'|resource_type| (|rank|) {0} / {1} / {2}'}
            ,'POL':{'DisplayAttributes':['AssuredName','PolicyNumber','DistributorName','EffectiveDate'],'Format':'|resource_type| (|rank|) {0} / {1} / {2}'}
            ,'PRV':{'DisplayAttributes':['AssuredName','PolicyNumber','DistributorName','EffectiveDate'],'Format':'|resource_type| (|rank|) {0} / {1}'}}

}";

            SearchCriteria sc = Newtonsoft.Json.JsonConvert.DeserializeObject<SearchCriteria>(jsonData);
        }
    }

    interface ISearchCriteria
    {
        string Term { get; set; }
        IEnumerable<string> ResourceTypes { get; set; }
        IDisplayInstructions DisplayInstructions { get; set; }
    }

    class SearchCriteria : ISearchCriteria
    {
        public string Term { get; set; }
        public IEnumerable<string> ResourceTypes { get; set; }

        public IDisplayInstructions DisplayInstructions
        {
            get { return this.displayInstructions as IDisplayInstructions; }
            set
            {
                this.displayInstructions = new DisplayInstructions();
                foreach (var kvp in value)
                {
                    this.displayInstructions.Add(kvp.Key, kvp.Value);
                }
            }
        }

        private DisplayInstructions displayInstructions;

        [JsonConstructor]
        public SearchCriteria(string term, IEnumerable<string> resourceTypes, IDisplayInstructions displayInstructions)
        {
            this.Term = term;
            this.ResourceTypes = resourceTypes;
            this.DisplayInstructions = displayInstructions;
        }
    }

    interface IDisplayInstructions : IDictionary<string, IDisplayInstruction> { }

    class DisplayInstructions : Dictionary<string, IDisplayInstruction> { }

    interface IDisplayInstruction
    {
        IEnumerable<string> DisplayAttributes { get; set; }
        string Format { get; set; }
    }

    class DisplayInstruction : IDisplayInstruction
    {
        public IEnumerable<string> DisplayAttributes { get; set; }
        public string Format { get; set; }
    }
}
dbc
  • 104,963
  • 20
  • 228
  • 340
John Hennesey
  • 343
  • 1
  • 2
  • 18
  • 3
    Possible duplicate of [JSON.NET - how to deserialize collection of interface-instances?](https://stackoverflow.com/questions/15880574/json-net-how-to-deserialize-collection-of-interface-instances) – Matthew Groves Feb 02 '18 at 20:48
  • 2
    Please reference the "many similar questions" and what you didn't understand about their answers. Otherwise, the question will be closed as a duplicate of at least one of them... – Heretic Monkey Feb 02 '18 at 20:49
  • Updated description to be more accurate. What makes this different than the suggested duplicate is this is a field in a class (this is declared as an interface), not the class itself. – John Hennesey Feb 02 '18 at 21:00
  • 1
    Seems like you are just newing up a DisplayInstructions inside the property anyway so can't you just change the return type to `DisplayInstructions` rather that `IDisplayInstructions`. It's also probably better to make the class your deserializing into a POCO and then pass that into the more complicate class rather that having one class trying to do too many things. – pmcilreavy Feb 02 '18 at 21:02
  • When I change the return type to DisplayInstructions I get: Error CS0738 'SearchCriteria' does not implement interface member 'ISearchCriteria.DisplayInstructions'. 'SearchCriteria.DisplayInstructions' cannot implement 'ISearchCriteria.DisplayInstructions' because it does not have the matching return type of 'IDisplayInstructions' I would like it to be interface driven as unit testing will happen through all this code. – John Hennesey Feb 02 '18 at 21:06
  • 1
    I haven't really seen a case where it's advantageous for unit testing purposes to have properties be interfaces instead of concrete types. If a type has logic in it, then it probably shouldn't be a property of something else. – mason Feb 02 '18 at 21:23
  • 1
    Other related questions: [How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?](https://stackoverflow.com/q/8030538/3744182) and [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182). – dbc Feb 02 '18 at 21:24
  • Ok, rethinking my implementation. Thank you for the constructive feedback @mason and dbc – John Hennesey Feb 02 '18 at 21:28
  • Updated to reflect the answer to the question. While the suggested post was helpful, it was only half of what I was missing. Now if you could kindly remove the down vote as it isn't accurate it would be helpful for others who need to know about the [JsonDictionary(ItemConverterType = typeof(ConfigConverter))] piece. – John Hennesey Feb 05 '18 at 17:55

1 Answers1

0

The post provided above from Matthew Groves (JSON.NET - how to deserialize collection of interface-instances) had half of the answer, and the other half came in the form of the JsonDictionary attribute (Applying JsonDictionary attribute to dictionary). I was able to keep the interface approach as it does fit the unit testing methodology I am after.

The final code:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string jsonData = @"{
    'Term' : 'john'
   ,'resourceTypes' : ['POL', 'CLM', 'WRK']
   ,'displayInstructions': {'AGB' : {'displayAttributes' : ['AssuredName','PolicyNumber','DistributorName','EffectiveDate'] ,'format':'|resource_type| (|rank|) {0} / {1}'}
                           ,'POL' : {'displayAttributes' : ['AssuredName','PolicyNumber','DistributorName','EffectiveDate'] ,'format':'|resource_type| (|rank|) {0} / {1}'}}
}";

            SearchCriteria des = JsonConvert.DeserializeObject<SearchCriteria>(jsonData);
        }
    }

    interface ISearchCriteria
    {
        string Term { get; set; }
        IEnumerable<string> ResourceTypes { get; set; }
        IDisplayInstructions DisplayInstructions { get; set; }
    }

    public class ConfigConverter<I, T> : JsonConverter
    {
        public override bool CanWrite => false;
        public override bool CanRead => true;
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(I);
        }
        public override void WriteJson(JsonWriter writer,
            object value, JsonSerializer serializer)
        {
            throw new InvalidOperationException("Use default serialization.");
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jsonObject = JObject.Load(reader);
            var deserialized = (T)Activator.CreateInstance(typeof(T));
            serializer.Populate(jsonObject.CreateReader(), deserialized);
            return deserialized;
        }
    }

    class SearchCriteria : ISearchCriteria
    {
        public string Term { get; set; }
        public IEnumerable<string> ResourceTypes { get; set; }

        [JsonConverter(typeof(ConfigConverter<IDisplayInstructions, DisplayInstructions>))]
        public IDisplayInstructions DisplayInstructions { get; set; }
    }

    interface IDisplayInstructions : IDictionary<string, IDisplayInstruction> { }

    [JsonDictionary(ItemConverterType = typeof(ConfigConverter<IDisplayInstruction, DisplayInstruction>))]
    class DisplayInstructions : Dictionary<string, IDisplayInstruction>, IDisplayInstructions
    {

    }

    interface IDisplayInstruction
    {
        IEnumerable<string> DisplayAttributes { get; set; }
        string Format { get; set; }
    }

    [JsonConverter(typeof(ConfigConverter<IDisplayInstruction, DisplayInstruction>))]
    class DisplayInstruction : IDisplayInstruction
    {
        public IEnumerable<string> DisplayAttributes { get; set; }
        public string Format { get; set; }
    }
}
John Hennesey
  • 343
  • 1
  • 2
  • 18