3

I have a C# .Net class RootObject which contains a number of lists of various object types. I get a json object from an ajax call and deserialize it into a RootObject instance. I would like to iterate over all the lists in my root object and create data tables for them to pass to a stored procedure, but I can't figure out how to do that. Can anyone help?

public class RootObject
{
    public List<Car> Car { get; set;}
    public List<Plane> Plane { get; set;}
    public List<Train> Train { get; set;}
}

    /// <summary>
    /// Returns a data table filled with the values from the input list
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <returns></returns>
    public DataTable CreateDataTable<T>(IEnumerable<T> list)
    {
        Type type = typeof(T);
        var properties = type.GetProperties();

        DataTable dataTable = new DataTable();
        foreach (PropertyInfo info in properties)
        {
            dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
        }

        foreach (T entity in list)
        {
            object[] values = new object[properties.Length];
            for (int i = 0; i < properties.Length; i++)
            {
                values[i] = properties[i].GetValue(entity);
            }

            dataTable.Rows.Add(values);
        }

        return dataTable;
    }



    /// <summary>
    /// This function saves the layout specified by the the user with the ID specified
    /// </summary>
    /// <param name="encryptedBEMSID"></param>
    /// <param name="Layout"></param>
    /// <returns></returns>
    public string Save(string encryptedBEMSID, string Layout)
    {
        // Make a list of table variable parameters to hold the results of the deserialization
        List<SqlParameter> parameters = new List<SqlParameter>();

        // Deserialize the json object
        RootObject obj = JsonConvert.DeserializeObject<RootObject>(Layout);
        PropertyInfo[] objLists = obj.GetType().GetProperties();

        foreach (PropertyInfo pi in objLists)
        {
            string ObjectType = pi.Name; // This would be "Car", "Plane", or "Train"
            string UpperObjectType = ObjectType.ToUpper();

// HERE'S WHERE I NEED HELP! I'd Like to replace Car with the class specified by ObjectType 
            List<Car> List = obj.Car;
            DataTable dt = CreateDataTable<Car>(List);

            // do stuff with the resulting data table

        }
    }

EDIT TO ADD JSON

{
    "Car": [
        {"name": "The General", "color": "red"},
        {"name": "Batmobile", "color": "blue"}
    ],
    "Plane": [
        {"name": "Air Force One", "color": "white"},
        {"name": "Spirit of St. Louis", "color": "gray"},
        {"name": "Wright Flyer", "color": "brown"}
    ],
    "Train": [
        {"name": "Orient Express", "color": "black"},
        {"name": "Ye Olde Bullet Train", "color": "orange"}
    ]
}
nickvans
  • 898
  • 13
  • 24
  • Please post sample JSON so that we can help you better. – ndd Nov 11 '15 at 00:21
  • http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic-method ? – Paul Abbott Nov 11 '15 at 00:24
  • " I would like to iterate over all the lists" - `RootObject ro = new RootObject(); foreach (Car car in ro.Car) { // do something };`? – Tim Nov 11 '15 at 00:25
  • @Tim It's not iterating over the items in the lists that I'm having trouble with, it's iterating over the lists themselves. I'd like to not have to explicitly enter ro.Car, ro.Plane and ro.Train (my actual use case has dozens of these types). – nickvans Nov 11 '15 at 00:31
  • There is no real way to do that outside of using reflection. – Chris Knight Nov 11 '15 at 00:39
  • 1
    If you're using JSON.NET, you can use `JObject.Parse` to get an enumerable object. Use `foreach` to iterate the key/value pairs - the keys would be "Car", "Plane", and "Train", whereas the values would be the arrays containing your items. – cbr Nov 11 '15 at 00:46
  • @Chris Knight: I'm fine using reflection, but I don't know how to go about it. – nickvans Nov 11 '15 at 00:54
  • @cubrr: I'm happy to use JSON.NET and get an enumerable object, but I still don't know how to cast my list with the proper type depending on the key. – nickvans Nov 11 '15 at 00:55

3 Answers3

0
foreach(var vehicle in vehicles) { //iterate car, plane, train
    foreach(var instance in vehicle) { //iterate the general, batmobile, etc.
        /* do stuff */
    }
}
Aaron Gates
  • 469
  • 4
  • 15
0

Assuming your objects are just {name, color} you could create a class for that, say, Vehicle and then deserialize into a Dictionary<string,List<Vehicle>>.

Now you can enumerate the dictionary easily and enumerate lists of Vehicles off each key.

If it's more complicated than the JSON you showed then you might be better off reading the JSON directly (using Json.NET) and handling it that way.

You could also add a type discriminator to your JSON (see https://www.mallibone.com/post/serialize-object-inheritance-with-json.net) and then Json.NET will know that the Car field contains Car objects (a fact implied but not stated in your current JSON schema). You may also be able to provide a custom serialization binder that handles your special situation with types defined by field names. You'd still need a base class Vehicle but the actual deserialized value would be of the type expected.

And, yet another option might be to use a Dictionary<string, Dictionary<string, object>> to hold the deserialized JSON since all you are doing is copying the values to a table anyway - you don't care that it's a Car, it's just a collection of names and values.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • Yeah, my contrived and simple example was overly simplified. In my actual case the attributes of each object are are vastly different. Assuming I had a base class that all the other classes could inherit from, I'm still unclear how I could cast(?) the objects to their actual types. – nickvans Nov 11 '15 at 05:16
0

If you move your datatable creation into a generic class VehicleHandler<T> you can create an instance of it with a matching type T to the type of the property you are currently examining like so:-

Type vehicleListType = pi.PropertyType;
Type vehicleType = vehicleListType.GetGenericArguments()[0];
var vehicleHandlerType = typeof (VehicleHandler<>).MakeGenericType(vehicleType);
var vehicleHandler = (VehicleHandler)Activator.CreateInstance(vehicleHandlerType);

And now, you can set the vehicles for this field and generate the data table:

vehicleHandler.SetVehicles(pi.GetValue(obj));
var dataTable = vehicleHandler.DataTable;

And the code for VehicleHandler is:

public abstract class VehicleHandler
{
    public abstract void SetVehicles(object listOfVehicles);
    public abstract DataTable DataTable { get; }
}

public class VehicleHandler<T> : VehicleHandler
{
    List<T> vehicles;

    public override void SetVehicles (object listOfVehicles)
    {
        this.vehicles = (List<T>) listOfVehicles;
    }

    public override DataTable DataTable => CreateDataTable(vehicles);

    public DataTable CreateDataTable(IEnumerable<T> list)
    {
        Type type = typeof (T);
        var properties = type.GetProperties();

        DataTable dataTable = new DataTable();
        foreach (PropertyInfo info in properties)
        {
            dataTable.Columns.Add(new DataColumn(info.Name,
                Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
        }

        foreach (T entity in list)
        {
            object[] values = new object[properties.Length];
            for (int i = 0; i < properties.Length; i++)
            {
                values[i] = properties[i].GetValue(entity);
            }

            dataTable.Rows.Add(values);
        }

        return dataTable;
    }

}
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • You are a fantastic human being. Thanks so much! I'll try it out first thing tomorrow morning. Thanks again! – nickvans Nov 11 '15 at 06:47