2

I have following JSON and I am using Json.NET (Newtonsoft.Json):

{
  "total_items": "62",
  "page_number": "6",
  "page_size": "10",
  "page_count": "7",
  "cars": {
    "car": [     
      {
        "car_name": "Honda", 
        "engines": {
          "engine": [  <-- HONDA has multiple engines, so this is an array
            {
              "name": "1.2L"
            },
            {
              "name": "1.8L"
            }
          ]
        },
        "country": "Japan"
        "image": {
            "thumb": {
                "url": "http://image_path/Honda.jpg" <-- Image provided
            }
        }
      },
      {
        "car_name": "Ford",
        "engines": {
          "engine": {   <-- FORD has single engine, so this is an object
              "name": "2.2L"
          }
        },
        "country": "Japan"
        "image": null  <-- image is null
      },
      { 
        "car_name": "VW",
        "engines": null,  <-- VW has no engines, so this is null
        "country": "Germany"  
        "image": null    <-- image is null
      }
    ]
  }
}

And I have following Car object:

class Car
{
    public Car() { }

    public string Name { get; set; }
    public string Country { get; set; }
    public List<String> EngineNames { get; set; }
}

I need to handle all 3 cases above (array for HONDA, object for FORD, null for VW). If it is not null, then get all engine names. So, for example above, my EngineNames list for the 3 cars would be:

Honda.EngineNames = {"1.2L", "1.8L"} // array in JSON
Ford.EngineNames = {"2.2L"} //object in JSON
VW.EngineNames = null //null in JSON

I need to parse the JSON above to get car data. I am parsing car_name and country but I don't know how to parse all engine names by handling the 3 situations above.

private Cars GetCars(string json)
{
    dynamic data = (JObject)JsonConvert.DeserializeObject(json);

    foreach (dynamic d in data.cars.car)
    {
        Car c = new Car(); 

        c.Name = (string)d.SelectToken("car_name");
        c.Country = (string)d.SelectToken("country");

        // PROBLEM: This works fine for array or null in JSON above (HONDA and VW), but it errors on JSON object (in case of FORD)
        // When handling FORD, I get error "'Newtonsoft.Json.Linq.JProperty' does not contain a definition for 'name'"
        c.EngineNames = (d.engines != null ? ((IEnumerable)d.engines.engine).Cast<dynamic>().Select(e => (string)e.name) : null);

        CarList.Add(c);
    }
    return CarList;
}
7
  • Why don't you make single item engines into an array of one item? Like: "engine": [{ "name": "2.2L" }] Commented Dec 21, 2015 at 23:18
  • @Steve JSON is provided to me, I did not make it. It is how it is, I need to parse it. Thanks Commented Dec 21, 2015 at 23:19
  • 4
    Possible duplicate of How to handle both a single item and an array for the same property using JSON.net Commented Dec 21, 2015 at 23:20
  • 1
    Linked question provides a nice solution to this Commented Dec 21, 2015 at 23:20
  • 1
    It appears the issue is that the classes don't exactly match the JSON - see answer Commented Dec 21, 2015 at 23:51

2 Answers 2

2

Using the converter from here (originally a proposed duplicate, but this question had some other issues with the JSON)

Your class structure needs to be modified a bit.

Looking at this JSON:

"cars": { <-- cars is an object, not an array
    "car": [ <-- the cars object actually contains the array
      {
        "car_name": "Honda", 
        "engines": { <-- same goes for this
          "engine": [
            {

Therefore, you'll need to write wrapper classes to properly reflect the JSON. Here's what I've come up with:

public class Root 
{
    public CarHolder Cars {get;set;}
}
public class CarHolder
{
    public IList<Car> Car { get; set; }
}
public class Car
{
    public Car() { }

    public string car_name { get; set; }
    public string Country { get; set; }

    public EngineHolder Engines { get; set; }
}
public class EngineHolder 
{
    [JsonConverter(typeof(SingleOrArrayConverter<Engine>))]
    public List<Engine> Engine { get; set; }
}
public class Engine
{
    public string Name { get; set; }
}

And using the convert from the above question:

public 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();
    }
}

Usage:

var result = JsonConvert.DeserializeObject<Root>(jsonStr);
Console.WriteLine(result.Cars.Car[0].Engines.Engine[0].Name == "1.2L");
Console.WriteLine(result.Cars.Car[0].Engines.Engine[1].Name == "1.8L");
Console.WriteLine(result.Cars.Car[1].Engines.Engine[0].Name == "2.2L");
Console.WriteLine(result.Cars.Car[2].Engines == null);

All print true

Looping through the cars & engines

foreach(var car in result.Cars.Car)
{
    if (car.Engines != null)
    {
        foreach(var engine in car.Engines.Engine)
        {
            var engineName = engine.Name;
        }
    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

Yes, I am getting same but I have problem figuring how to loop through your "result" var and print car_name, country, and all the engines for each car? Thank you so much
@dbnex14 I've added an example to loop through the results
@dbnex14 You'd just just need to create an image and a thumb class. image would have a property thumb of type thumb, and thumb would have a url property of type string. Then you'd just need the null check on car.image != null.
@dbnex14 No worries mate :)
Not sure mate, I haven't really worked with WPF, sorry
|
1

You should be able to use this as your class structure;

public class Rootobject
{
    public string total_items { get; set; }
    public string page_number { get; set; }
    public string page_size { get; set; }
    public string page_count { get; set; }
    public Cars cars { get; set; }
}

public class Cars
{
    public Car[] car { get; set; }
}

public class Car
{
    public string car_name { get; set; }
    public Engines engines { get; set; }
    public string country { get; set; }
}

public class Engines
{
    public object engine { get; set; }
}

//I created below class manually
public class Engine
{
    public string name { get; set; }
}

I used inbuilt functionality of VS to generate this. Steps;

  1. Open a new cs file.
  2. Copy your json
  3. Go to Edit menu> Paste special
  4. Select Paste JSON as classes

Once this is done, it should be just a matter of creating two methods to serialize and deserialize.

Updated with serialise/deserialise methods

        private static T Deserialise<T>(string json)
        {
            var myopject = JsonConvert.DeserializeObject<T>(json);
            return myopject;
        }

        private static string Serialise<T>(T value)
        {
            var mycontent =  JsonConvert.SerializeObject(value);
            return mycontent;
        }

Now to test above methods, you can do this.

            var jsonstring = @"{
              ""total_items"": ""62"",
              ""page_number"": ""6"",
              ""page_size"": ""10"",
              ""page_count"": ""7"",
              ""cars"": {
                ""car"": [
                  {
                    ""car_name"": ""Honda"",
                    ""engines"": {
                      ""engine"": [
                        {
                          ""name"": ""1.2L""
                        },
                        {
                          ""name"": ""1.8L""
                        }
                      ]
                    },
                    ""country"": ""Japan""
                  },
                  {
                    ""car_name"": ""Ford"",
                    ""engines"": {
                      ""engine"": {
                        ""name"": ""2.2L""
                      }
                    },
                    ""country"": ""Japan""
                  },
                  {
                    ""car_name"": ""VW"",
                    ""engines"": null,
                    ""country"": ""Germany""
                  }
                ]
              }
            }";
            var myobject = Deserialise<Rootobject>(jsonstring);

            //if you want to parse engines you can do something like    this.

          if (myobject.cars != null && myobject.cars.car != null && myobject.cars.car.Any())
        {
            foreach (Car car in myobject.cars.car)
            {
                if (car.engines != null && car.engines.engine != null)
                {
                    bool isList = false;
                    try
                    {
                        var eng = Deserialise<Engine>(car.engines.engine.ToString());
                    }
                    catch
                    {
                        isList = true;
                    }
                    if (isList)
                    {
                        try
                        {
                            var eng = Deserialise<List<Engine>>(car.engines.engine.ToString());
                        }
                        catch
                        {
                            Debug.WriteLine("Not a list");
                        }
                    }
                }
            }
        }

            var myjson = Serialise(myobject);

3 Comments

Thanks. How would I deserialize this created class. Would you mind providing example as I am new to this. Thanks,
I get error "Expected class, delegate, enum, interface, or struct" and T in private static T Deserialise<T>(string json) is red-underlined
First, before trying to implement this change in to your project, create a console app. Just copy paste my code and see if it works. Once you understand the technique fully, you can implement this in your real project. (For the time being ignore engine parsing code that i added just now.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.