Search code examples
c#jsonwhois

How to deserialize a JSON object containing an object or object array


I'm trying to deserialize the JSON response that I get back from the ARIN whois REST API. I'm new to JSON but I think they are returning two different schemas depending on the results and I'm having a hard time taking it apart.

Here is an abridged version of the JSON that's returned if there are multiple net objects:

{
  "nets": {
    "@xmlns":{"ns3":"http:\/\/www.arin.net\/whoisrws\/netref\/v2",
    "net": [{
        "customerRef": {
          "@name": "Internet Customer",
        },
      }, {
        "orgRef": {
          "@name": "Internet Service Provider",
        },
      }
    ]
  }
}

Note that nets is an array of net objects. When there is only one net object returned, the JSON looks like this:

{
  "nets": {
    "@xmlns":{"ns3":"http:\/\/www.arin.net\/whoisrws\/netref\/v2",
    "net": {
        "orgRef": {
          "@name": "Internet Customer",
        },
      }
  }
}

In this case nets is an object that contains a single net object. I can define my class with an array or with a single embedded object but deserializing with JsonConvert.DeserializeObject<ARINWhois>(response) will throw an exception on results that don't agree with my schema.

I've considered two options:

  1. Deserialize it all by hand with JsonTextReader.
  2. Define both object schemas and retry with the other one if an exception is thrown.

Is there is an easier and more elegant solution?


Solution

  • Thanks to Eser22 who pointed me toward the use of JsonConverter which does exactly what I need. How to Handle both a single item and an array for the same property is an excellent explanation. Works perfectly!

    Here is the final version of my code.

    /*
     * parse the response
     */
    ARINWhois WhoisResp = JsonConvert.DeserializeObject<ARINWhois>(response);
    
    /*
     *  custom converter
     */
    class SingleOrArrayConverter<T> : JsonConverter
    {
      public override bool CanConvert(Type objectType)
      {
        return (objectType == typeof(List<T>));
      }
    
      public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
      {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
          return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
      }
    
      public override bool CanWrite
      {
        get { return false; }
      }
    
      public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
      {
        throw new NotImplementedException();
      }
    }
    
    /*
     * data models
     */
    public class ARINWhois
    {
      [JsonProperty("nets")]
      public ARINNets Network { get; set; }
    }
    public class ARINNets
    {
      [JsonProperty("net")]
      [JsonConverter(typeof(SingleOrArrayConverter<ARINNet>))]
      public List<ARINNet> Networks;
    }
    
    public class ARINNet
    {
      [JsonProperty("registrationDate")]
      public ARINregistrationDate RegistrationDate;
      [JsonProperty("orgRef")]
      public ARINorgRef Organization;
      [JsonProperty("customerRef")]
      public ARINcustomerRef Customer;
    }
    public class ARINregistrationDate
    {
      [JsonProperty("$")]
      public string date { get; set; } 
    }
    public class ARINorgRef
    {
      [JsonProperty("@name")]
      public string name { get; set; }
      [JsonProperty("@handle")]
      public string handle { get; set; }
    }
    public class ARINcustomerRef
    {
      [JsonProperty("@name")]
      public string name { get; set; }
      [JsonProperty("@handle")]
      public string handle { get; set; }
    }