Search code examples
c#.netjson.net.net-4.5.2

Deserialize first property not matching any target object's properties into specific property


I'm doing some web API integration with Newtonsoft.Json, and as always, I have to do dumb stunts to properly deserialize what they're sending back.

In this case, the API will send responses resembling this kind of structure:

{ "contacts": [ ... ], "has-more": true, "offset": 38817 }

The "has-more" and "offset" properties are pretty much constant on the different method responses, and have been defined accordingly on the response object that I'm deserializing into. The response object looks something like this:

public class APIResponse {
    public JContainer Result { get; set; }
    [JsonProperty("has-more")]
    public bool HasMore { get; set; }
    [JsonProperty("offset")]
    public int Offset { get; set; }
}

That first "contacts" property is what can vary; for some methods, I might get "contacts", some might get "companies", and others might get who-knows-what. I also don't have any way to be certain that every response will have such a "variable" property, nor that it will be the first one, positionally speaking.

For this example, what I would like to happen is the deserializer looks at the Json and says, "Let's see, I don't see anything mapping to 'contacts', so we'll put that into 'Result', and then I can see from the JsonProperty attributes that 'has-more' and 'offset' go into HasMore and Offset. Okay, all set, here's your object."

I suspect this involves some tricks with a custom JsonConverter or IContractResolver, but I'm just not connecting the dots here. I tried doing a simple custom contract resolver, but it appears to use contract resolvers to resolve object property names into property names to look for in the JSON text, not vice-versa.


Solution

  • You can use a base class + derivations for each response type.

    public class APIResponseBase {
        [JsonProperty("has-more")]
        public bool HasMore { get; set; }
        [JsonProperty("offset")]
        public int Offset { get; set; }
    }
    
    public class ContactsResponse : APIResponseBase {
      public IEnumerable<Contact> Contacts { get; set; }
    }
    
    public class CompaniesResponse : APIResponseBase {
      public IEnumerable<Company> Companies { get; set; }
    }
    
    var contactsResponse = JsonConvert.Deserialize<ContactsResponse>(json);
    IEnumerable<Contact> contacts = contactsResponse.Contacts