geostocker geostocker - 4 months ago 7
JSON Question

Dynamically creating JSON objects from reflected methods

First off, I'm sorry if I'm off on my terminology - but bare with me.

I've since getting a bunch of help from the SO hivemind been able to create a REST API (C#, MVC) that handles dynamic (at least that's what I want to call it) calls through catching the string param and finding the correct method through using reflection.

var myType = typeof(JaberoDC.JaberoDC.JaberoDC);


var method = myType
.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Single(mi =>mi.ReturnType == typeof(DataSet)
&& string.Equals(mi.Name, param, StringComparison.OrdinalIgnoreCase));
var subject = Activator.CreateInstance(myType);
var result = method.Invoke(subject, new Object[] { "", conStr, "", 0, 0, null });


DataSet ds = (DataSet)result;


What I need help with now is any sort of advice on how to handle the various results dynamically. Meaning that I need some help with understanding how to either create a class that handles 0=>N columns from the rows in the DataTable (after getting the DT from the DS), or how to serialize the data without creating instances of above mentioned class.

Again, I'm sorry if my terminology is off here.

I've basically been thrown into the deep-end at my current employer to create a REST API for a DataComponent that has a few hundred methods. As it is now the API only works when using below code:

List<Worksite> arr2 = new List<Worksite>();

foreach (DataTable table in ds.Tables)
{

foreach (DataRow row in table.Rows)
{
string id = row["JobID"].ToString();
string name = row["JobName"].ToString();
string site = row["SiteName"].ToString();

arr2.Add(new Worksite
{
ID = id,
JobName = name,
SiteName = site
});
}
}


Basically this only handles worksites (a specific DataSet in the DataComponent).

My current solution:

public string GetFromParam(String param)
{

var myType = typeof(JaberoDC.JaberoDC.JaberoDC);


var method = myType
.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Single(mi =>mi.ReturnType == typeof(DataSet)
&& string.Equals(mi.Name, param, StringComparison.OrdinalIgnoreCase));
var subject = Activator.CreateInstance(myType);
var result = method.Invoke(subject, new Object[] { "", conStr, "", 0, 0, null });


DataSet ds = (DataSet)result;

List<GenerateModel> arr2 = new List<GenerateModel>();
int count = 0;

List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();
Dictionary <string, object> dicRow;
foreach (DataTable table in ds.Tables)
{
foreach (DataRow row in table.Rows)
{
dicRow = new Dictionary<string, object>();
foreach (DataColumn col in table.Columns){
dicRow.Add(col.ColumnName, row[col]);
}
rows.Add(dicRow);
}

}

// for (int i = 0; i < ds.Tables.)

string json = JsonConvert.SerializeObject(rows);
return json;
}


I'm basically using the proposed solution in the marked answer whilst also using a dynamic solution to the issue with datakey-value pairs by using a dictionary to hold them.

Answer

I agree with Clint's comment about using Json.net by Newtonsoft. That has a lot of helper methods that will make your life easier.

Ideally, you will be creating one controller per type of data that you expect to receive. Then you can use the incoming data to create an instance of a class that is modeled after what you will be receiving. With RESTful APIs and MVC, I treat the incoming data as a FormDataCollection, which is like an incoming stream of keyvalue pairs that you must process sequentially. Here is a code sample to point you in the right direction:

// POST api/mycontrollername
public HttpResponseMessage Post(FormDataCollection fdc)
{
    try
    {
        if (fdc != null)
        {
            MyClass myInstance = new MyClass();

            IEnumerator<KeyValuePair<string, string>> pairs = fdc.GetEnumerator();
            while (pairs.MoveNext())
            {
                switch (pairs.Current.Key)
                {
                    case "PhoneNumber":
                        myInstance.PhoneNumber = pairs.Current.Value;
                        break;
                    ...
                    default:
                        // handle any additional columns
                        break;
                }
            }

            // do stuff with myInstance
            // respond with OK notification
        }
        else
        {
            // respond with bad request notification
        }
    }
    catch (Exception ex)
    {
        // respond with internal server error notification
    }
}

If you must handle multiple data types with the same controller, there may be a particular data field that would give you a clue as to what you are receiving so that you can handle it appropriately. If you genuinely have no idea what you are receiving at any time, you may be able to cast it as a dynamic type and use reflection, but it sounds like the data sender is setting you up for a tough integration in that case - it is definitely not how I would engineer an API...

EDIT: (To clarify based on your comments) It appears you want to accept requests for any number of data types using a single controller, and return a DataTable with the results, even though you don't know in advance which fields the DataTable will contain.

First, unless it is a specific requirement, I wouldn't use a DataTable because it is not a very platform-independent solution - if the requesting application is in a non-.NET language, it will have an easier time parsing a Json array compared to a DataTable. If you must use a DataTable, you can look into .Net's DataTable.ToXML() extension method, but I have run into issues with special characters not converting to XML well and some other gotchas with this approach.

If you want to go the recommended json route, the data you request will have to be sufficient to query the database (ie: RequestType = "jobSiteReport", MinDate = "7/1/2016", MaxDate = "7/12/2016"). Use this to query the database or generate the data (into a DataSet or DataTable if that is what you are comfortable with) depending on what the "RequestType" is. Use Linq or a loop or some other method to transform the DataTable into an object array (this could be an anonymous type if you want, but I prefer keeping things strongly typed when I have the option.) This is pseudocode, but should give you an idea of what I mean:

//using Newtonsoft.Json;

List<MyObject> objects = new List<MyObject>();
for (int i = 0; i < dt.Rows.Count; i++)
{
    MyObject obj = new MyObject()
    obj.Time = dt.Rows[i]["Time"];
    obj.Person = dt.Rows[i]["PersonID"];
    ...
    objects.Add(obj);
}

string json = JsonConvert.SerializeObject(objects.ToArray());

Now you have a string that contains json data with all of the results from your DataTable. Then you can return that. (You could also use a SqlDataReader to accomplish the same result without using a DataTable as an intermediate step - populate the list from the SqlDataReader, convert the list to an array, then serialize the array to json.)

If you are inventing the schema, you should create documentation about what the requester should expect to receive.

Json.Net also has methods for deserializing strongly typed objects from json, deserializing anonymous types, etc. You should read up on what is available - I'm sure that it has something that will be perfect for what you are trying to achieve.