I am working with an external API that returns a property either as an array or as an object, depending on the count. What is a good way to handle this?
Returning as array:
{
"contacts": {
"address": [
{
"id": "47602070",
"type": "Work",
"street": "MyStreet",
"city": "MyCity",
"zip": "12345",
"country": "USA"
},
{
"id": "47732816",
"type": "GPS",
"street": "50.0,30.0"
}
]
}
}
Returning as object:
{
"contacts": {
"address": {
"id": "47602070",
"type": "Work",
"street": "MyStreet",
"city": "MyCity",
"zip": "12345",
"country": "USA"
}
}
}
I'm thinking a workaround would be to use a custom deserializer and return an array of length 1 for the object case, and default deserialization for the array case, but I don't know how to do that yet.
I tried deserializing the object to an array and hoping Json.net would handle this case for me, but no dice.
A custom JSON.NET converter might do the trick here. It's not that hard.
For a DateTime property you might do it as follows. Just decorate the property in question with the custom converter.
[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
[JsonProperty(PropertyName = "creation_date")]
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime CreationDate { get; set; }
}
JSON.NET provides most of the plumbing. Just derive from a base converter.
public class UnixDateTimeConverter : DateTimeConverterBase
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{ ...}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{ ... }
}
All you have to do is implement the ReadJson (deserialization) and WriteJson (serialization) methods.
You can find a complete example here:
Writing a custom Json.NET DateTime Converter
For your particular problem you need a bit more control. Try the following type of converter:
public class Contact
{
private List<Address> _addresses = new List<Address>();
public IEnumerable<Address> Addresses { get { return _addresses; }
}
public class ContactConverter : CustomCreationConverter<Contact>
{
public override Contact Create(Type objectType)
{
return new Contact();
}
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
var mappedObj = new Contact();
// Parse JSON data here
// ...
return mappedObj;
}
}
Using a custom converter like the one above you can parse the JSON data yourself and compose the Contact object(s) as you please.
I modified an example I found here:
JSON.NET Custom Converters–A Quick Tour
In this case you need to pass the custom converter when desearilizing.
Contact contact =
JsonConvert.DeserializeObject<Contact>(json, new ContactConverter());
Based on Christophe Geers' answer, here is what I ended up doing.
Create a custom JSON converter for always parsing the JSON as an array. If the JSON is a non-array object, then deserialize the object and wrap it in an array.
Mark the corresponding properties with a custom converter attribute.
Custom converter
public class JsonToArrayConverter<T> : CustomCreationConverter<T[]>
{
public override T[] Create(Type objectType)
{
// Default value is an empty array.
return new T[0];
}
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
// JSON object was an array, so just deserialize it as usual.
object result = serializer.Deserialize(reader, objectType);
return result;
}
else
{
// JSON object was not an arry, so deserialize the object
// and wrap it in an array.
var resultObject = serializer.Deserialize<T>(reader);
return new T[] {resultObject};
}
}
}
Data structures for the question example
public class Organisation
{
public Contacts contacts;
}
public class Address
{
public string id;
public string street;
public string city;
public string type;
public string zip;
public string country;
}
public class Contacts
{
// Tell JSON.net to use the custom converter for this property.
[JsonConverter(typeof(JsonToArrayConverter<Address>))]
public Address[] address;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With