Prevent JSON.Net custom converter from converting child objects

907 Views Asked by At

I’m receiving JSON from a service which is a set of search results. Each object in the “results” array could be one of two types of objects (cat/dog I my example). Here’s some example JSON:

{
  "total": 2,
  "results": [
    {
      "type": "cat",
      "id": "1",
      "name": "Kitty"
    },
    {
      "type": "dog",
      "id": "2",
      "name": "Rover",
      "cat": {
        "id": "3",
        "name": "Missy"
      }
    }
  ]
} 

I’m deserializing the JSON root object to “SearchResult”. Each item in the "results" is a “SearchResultItem”.

Here’s what my models like for the items:

public class Cat : SearchResultItem
{
    public string id {get; set;}
    public string name {get; set;}  
}

public class Dog : SearchResultItem
{
    public string id {get; set;}
    public string name {get; set;}
    public Cat {get; set;}  
}

I’ve created a custom converter to convert to the correct type for each object:

public class SearchResultConverter : Newtonsoft.Json.Converters.CustomCreationConverter<SearchResultItem>
    {
        public override SearchResultItem Create(Type objectType)
        {
            throw new NotImplementedException();
        }

        public SearchResultItem Create(Type objectType, JObject jObject)
        {
            var type = (string)jObject.Property("type");
            switch (type)
            {
                case "cat":
                    return new Cat();
                case "dog":
                    return new Dog();
            }

            return null;

            //throw new ApplicationException(String.Format("The given vehicle type {0} is not supported!", type));
        }

        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
            var target = Create(objectType, jObject);

// Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);

            return target;
        }
    }

The issue I’m having is that Dog happens to have a child item which is a Cat. My converter is catching this Cat object and trying to convert it. Only the results object have a “type” property, any instance of a child Cat or Dog won’t have this “type” property. The absence of this property is causing an Null Reference exception.

How do I restrict the converter to only the top level items in the “results” array?

1

There are 1 best solutions below

0
On BEST ANSWER

Probably the easiest way to do this is to override JsonConverter.CanConvert and have it return true when typeof(SearchResultItem) == objectType, rather than when it is assignable from objectType:

public class SearchResultConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(SearchResultItem);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public SearchResultItem Create(Type objectType, JObject jObject)
    {
        // As before
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // As before
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotSupportedException("SearchResultConverter should only be used while deserializing.");
    }
}

Now the converter will be called only when attempting to deserialize an instance of SearchResultItem. For members already declared to be of type Dog or Cat its ReadJson() method will not get called.