2

We're dealing with an json API result. Just to make our lives difficult the provider of the API returns on of the items as an array if there are multiple objects, or as a single object if there is only one.

e.g.

If there is only one object...

{
  propertyA: {
    first: "A",
    second: "B"
  }
}

Or if there are multiple:

{
  propertyA: [
    {
      first: "A",
      second: "B"
    },
    {
      first: "A",
      second: "B"
    }
  ]
}

Does anybody have a good way of dealing with this scenario? Ideally we'd like to serialize both to

public class ApiResult{
  public ApiItem[] PropertyA {get;set;}
}

This works for the second example but of you encounter the first example you get a A first chance exception of type 'System.MissingMethodException' occurred in System.Web.Extensions.dll

Additional information: No parameterless constructor defined for type of 'ApiItem[]'.

chrisb
  • 937
  • 1
  • 10
  • 22
  • What library are you using to deserialize the JSON? If it's JSON.NET, [this question](http://stackoverflow.com/q/7816780/27779) might help. – Gregory Higley Nov 29 '14 at 16:58
  • Why don't always deserialize as array and avoid the issue? Although, I am not aware of JSON deserialization. This just my hunch. – Piyush Parashar Nov 29 '14 at 17:00
  • @PiyushParashar asit says in the question this is an API (i.e. from a third party) so we don't have any control over it. – chrisb Nov 29 '14 at 17:09
  • @GregoryHigley we're using json.net – chrisb Nov 29 '14 at 17:10
  • @chrisb May be I was not clear. What I am saying is irrespective of what the API returns (single element or array) you can always deserialize to an array to be safe. So if it is one object you get array of one element else more than one. I am assuming that the objects whether single or inside the array will be of same type. – Piyush Parashar Nov 29 '14 at 17:22
  • @PiyushParashar yes we'd love to do that. But how is the best way to do it? As it says in the question if you try to do that you get an exception. – chrisb Nov 29 '14 at 17:54
  • Can you please paste the full structure of the string? Including what type of object is being repeated. I am trying to relate it to xml which I understand much better. – Piyush Parashar Nov 29 '14 at 18:02
  • This answer provides an elegant solution to this problem: http://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n tanks @BrianRogers – chrisb Dec 01 '14 at 15:31

4 Answers4

2

I assume the class definition is as below

public class ApiResult
{
    public ApiItem[] PropertyA { get; set; }
}

public class ApiItem
{
    public string First { get; set; }
    public string Second { get; set; }
}

You can deserialize the json into a dynamic variable, then check the type of d.propertyA. If it's JArray then propertyA is an array, so you can deserialize the json into a ApiResult. If it's JObject then propertyA is a single object, so you need to manually construct ApiItem and assign it to PropertyA of ApiResult. Consider the method below

public ApiResult Deserialize(string json)
{
    ApiResult result = new ApiResult();
    dynamic d = JsonConvert.DeserializeObject(json);
    if (d.propertyA.GetType() == typeof (Newtonsoft.Json.Linq.JObject))
    {
        // propertyA is a single object, construct ApiItem manually
        ApiItem item = new ApiItem();
        item.First = d.propertyA.first;
        item.Second = d.propertyA.second;

        // assign item to result.PropertyA[0]
        result.PropertyA = new ApiItem[1];
        result.PropertyA[0] = item;
    }
    else if (d.propertyA.GetType() == typeof (Newtonsoft.Json.Linq.JArray))
    {
        // propertyA is an array, deserialize json into ApiResult
        result = JsonConvert.DeserializeObject<ApiResult>(json);
    }

    return result;
}

The code above will return an instance of ApiResult for both json examples.

Working demo: https://dotnetfiddle.net/wBQKrp

ekad
  • 14,436
  • 26
  • 44
  • 46
1

Building upon ekad's answer, I made the code:

  • Shorter: as now you don't have map one by one every property inside ApiItem
  • Probably faster in the case of being already an Array (by not calling to both versions of JsonConvert.DeserializeObject with the same input of the json string)
  • Explicit about how to introduce other properties
  • Handling the error case of unknown type of our propertyA (the one that can be either an array or an object).

Notice that instead of JsonConvert.DeserializeObject, I call JObject.Parse, and then ToObject<> for only the part I need in that particular case:

static ApiResult Deserialize(string json)
{
    JObject j = JObject.Parse(json);
    var propA = j["propertyA"];
    switch (propA.Type.ToString())
    {
        case "Object":
            return new ApiResult {
                PropertyA = new[]{propA.ToObject<ApiItem>()},
                SomethingElse = j["somethingElse"].ToObject<string>(),
            };
        case "Array":
            return j.ToObject<ApiResult>();
        default:
            throw new Exception("Invalid json with propertyA of type " + propA.Type.ToString());
    }
}

The API is pretty much the same, but I've added SomethingElse (for showing how other properties can be easily handled with this approach):

public class ApiResult
{
    public ApiItem[] PropertyA { get; set; }
    public string SomethingElse { get; set; }
}
public class ApiItem
{
    public string First { get; set; }
    public string Second { get; set; }
}

Working demo: https://dotnetfiddle.net/VLbTMu

Mariano Desanze
  • 7,847
  • 7
  • 46
  • 67
0

JSON# has a very lightweight tool that allows you to achieve this. It will retrieve embedded JSON, regardless of whether or not the embedded JSON is an object, or array, from within larger JSON objects:

const string schoolMetadata = @"{ "school": {...";
var jsonParser = new JsonObjectParser();

using (var stream = 
    new MemoryStream(Encoding.UTF8.GetBytes(schoolMetadata))) {
    Json.Parse(_jsonParser, stream, "teachers");
}

Here we retrieve a "teachers" object from within a larger "school" object.

Paul Mooney
  • 1,576
  • 12
  • 28
-1

best way to Serialize/Deserialize to/from JSON is Json.NET

Popular high-performance JSON framework for .NET

Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Sizes = new string[] { "Small" };

string json = JsonConvert.SerializeObject(product);
//{
//  "Name": "Apple",
//  "Expiry": "2008-12-28T00:00:00",
//  "Sizes": [
//    "Small"
//  ]
//}

string json = @"{
  'Name': 'Bad Boys',
  'ReleaseDate': '1995-4-7T00:00:00',
  'Genres': [
    'Action',
    'Comedy'
  ]
}";

Movie m = JsonConvert.DeserializeObject<Movie>(json);

string name = m.Name;
// Bad Boys
Mohamad Shiralizadeh
  • 8,329
  • 6
  • 58
  • 93