Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse non-array JSON object as array with Json.net

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.

like image 999
angularsen Avatar asked Jun 11 '12 13:06

angularsen


2 Answers

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());
like image 37
Christophe Geers Avatar answered Nov 04 '22 12:11

Christophe Geers


Based on Christophe Geers' answer, here is what I ended up doing.

  1. 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.

  2. 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;
}
like image 95
angularsen Avatar answered Nov 04 '22 13:11

angularsen