0

First of all - I know about a possible solution of my problem in this specific case that I'm going to show you (by saving namespaces, or via custom deserializer during deserialization, if you get what I mean), but that won't be future-proof for the planned needs, so please don't suggest those.

I've started using JSON.net not so long ago, and this is my first attempt at creation of custom converter, example of which I wasn't able to find anywhere on the web. I've used a couple of converters that were coded for similar needs, so if I've a couple of unneeded steps in here - be so kind to not only correct the problem itself but also tell me if there is anything that can be removed for performance benefit, thanks.

Now moving to the code (I've stripped down all irrelevant things). Here is the interface implementation:

[JsonConverter(typeof(StringEnumConverter))]
public enum SomeInterfaceType
{
    NoType = 0,
    Type1 = 1,
    Type2 = 2,
    //...
}
[JsonConverter(typeof(SomeInterfaceConverter))]
public interface SomeInterface
{
    SomeInterfaceType StoredType { get; }
    //...
}

As you can see, the interface stores some general information related to the interface, as well as StoredType, which we will use later for identification of specific class during deserialization.

Classes themselves are pretty straight forward:

public class Class1 : SomeInterface
{
    public SomeInterfaceType StoredType { get { return SomeInterfaceType.Type1; } }
   //...
}
public class Class2 : SomeInterface
{
    public SomeInterfaceType StoredType { get { return SomeInterfaceType.Type2; } }
   //...
}
//...

There are quite a few of those, and they have a lot of properties and function, but that is irrelevant to the question, so 2 are enough for this example.

Here is where those classes(interfaces) will be stored in the program itself:

public class LowerClass
{
    public Dictionary<SomeKey, List<SomeInterface>> interfaceDictionary;
    //...
}
public class MiddleClass
{
    private LowerClass ccc;
    //...
}
public class TopClass
{
    private MiddleClass sss;
    //...
}

The amount of classes that implement that interface will increase over time, and there are few other reasons for this choice. Saving namespaces works perfectly fine for this particular case, but serialized information can be used by various programs (various languages), so it's not the best solution, not even to mention if someone is going to change things manually (which should be possible), or if for whatever reason namespace changes in the future, so as I said - not the best solution.

Now moving to the core of the problem:

 public class SomeInterfaceConverter: JsonConverter
 {
    public SomeInterfaceConverter() { }
    JsonObjectContract FindContract(JObject obj, JsonSerializer serializer)
    {
        SomeInterfaceType typeQ = obj["StoredType"].ToObject<SomeInterfaceType>();
        switch (typeQ)
        {
            case SomeInterfaceType.Type1:
                {
                    return serializer.ContractResolver.ResolveContract(typeof(Class1)) as JsonObjectContract;
                }
            case SomeInterfaceType.Type2:
                {
                    return serializer.ContractResolver.ResolveContract(typeof(Class2)) as JsonObjectContract;
                }
            //...
            case SomeInterfaceType.NoType:
            default:
                {
                    return null;
                }
        }
    }
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(SomeInterface);
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null) return null;
        JObject obj = JObject.Load(reader);
        var contract = FindContract(obj, serializer);
        if (contract == null) throw new JsonSerializationException("No contract found in " + obj.ToString());
        existingValue = contract.DefaultCreator();
        using (var sr = obj.CreateReader())
        {
            serializer.Populate(sr, existingValue);
        }
        return existingValue;
    }
    public override bool CanWrite { get { return false; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

I'm not entirely sure if I need to use this contract thing, or not, but I basically want to omit custom-converter for any other classes out there. Also I would like to omit doing custom converter for whole main class, and would like to keep standard converter for anything but the classes that implement SomeInterface (at this point they're only present in that particular Dictionary, but that might change in the future as well).

FindContract is executed exactly as expected, reads everything fine from the json object, then probably returns the needed Contract? (found on the web). Then at this line:

existingValue = contract.DefaultCreator();

it throws NullReferenceException. Contract is definitely there (if it was produced properly). I've played with it for a couple of hours, trying different workarounds that I could find on stackoverflow and other sites, but I'm still stuck with this exception, no matter what I tried.

As I've said from the very beginning - I'm fairly new to JSON.net, and probably it should be easy. Maybe I don't even need that contract and should use DeserializeObject<> or something else instead (I've also looked at that, but couldn't find anything close enough, that could be easily transferred to my case). All the possible combinations of ClassesX and TypesX are known and can be hardcoded without any problems.

Thanks in advance for taking your time, and any valuable tips/corrections.

Update:

As requested in comments, here is JSON:

{
  "LowerClass": {
    "interfaceDictionary": {
      "Key1": [
        {
          "StoredType": "Type1"
        },
        {
          "StoredType": "Type2"
        }
      ]
    }
  }
}

Also I decided to add the way I deserialize, just in case (since JSON above omits the top class, not that it has any relevance to the question anyway):

MiddleClass whatever = JsonConvert.DeserializeObject<MiddleClass>(source);
Snow
  • 1
  • 3
  • Here's a very similar question with a working solution: [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182). For us to help with your specific problem, we need to see the JSON that fails deserialization. Otherwise we'd just be guessing. Perhaps you're using `StringEnumConverter` inconsistently? – dbc Apr 11 '17 at 18:44
  • Thanks for the link, I've tried to change JsonObjectContract to JsonContract as in that case, but that didn't solve it. Now that I think of it, could it be that JSON.net tries to create Dictionary with value of one of the types instead of interface and populate there few types? I will also update the question with JSON and the way it is deserialized. – Snow Apr 13 '17 at 15:01
  • Your code does not compile -- in `public Dictionary> interfaceDictionary;` the type `SomeKey` is undefined. Also, `private LowerClass ccc;` should be `public LowerClass LowerClass { get; set; }`. If I fix those, your code works without problem. See https://dotnetfiddle.net/2pFOTT. Please try to provide a [mcve] for your problem. – dbc Apr 13 '17 at 18:12

1 Answers1

0

I ended up digging through JSON.net code, and from what I understood - if there is any way to force contract.DefaultCreator() working as expected (not throwing NullReferenceException), then it will be a quite complicated thing to achieve.

Instead of trying to figure out what would be the easiest way to "fix" DefaultCreator, I simply created a workaround function and replaced it completely:

//replaced that:
existingValue = contract.DefaultCreator();
//with that:
existingValue = ContractToObject(type, contract);

The function itself:

object ContractToObject(SomeInterfaceType type, JsonObjectContract contract)
{
    switch (type)
    {
        case SomeInterfaceType.Type1:
            {
                return new Class1(Params);
            }
        case SomeInterfaceType.Type2:
            {
                return new Class2(Params);
            }
            //...
        case SomeInterfaceType.None:
        default:
            {
                return null;
            }
    }
}
Snow
  • 1
  • 3