Adventures in RESTful Services

Specifying fields in JSON responses from services

A standard RESTful service returns the representation of an entire object; all its fields in a representation of the class instance. So, if we have a service that returns student data, the response JSON to the following call:
/api/Students

might look like this:

[
{
id: 1,
lastName: "Alexander",
firstMidName: "Carson",
enrollmentDate: "2005-09-01T00:00:00",
enrollments: null
},
{
id: 2,
lastName: "Alonso",
firstMidName: "Meredith",
enrollmentDate: "2002-09-01T00:00:00",
enrollments: null
},
{
id: 3,
lastName: "Anand",
firstMidName: "Arturo",
enrollmentDate: "2003-09-01T00:00:00",
enrollments: null
}
]

Now, our service client may only be interested in the first and last names, so ideally we'd like to restrict the response object to only those fields. We can implement a "fields" query parameter, allowing us to specify the fields that we're interested in. That would look like this:

/api/Students?fields=lastName,firstMidName

Now, to implement this, we simply add the fields parameter in the controller like so:

        // GET: api/Students
        [HttpGet]
        public IEnumerable<Object> GetStudents(string fields)
 

Ok, so we now have a set of fields specified in a string. Let's parse that string (there are cooler ways to do this, but back to basics for now) Let's create a method for parsing the "fields" string:

        public object SummariseObject(object o,string fields)
        {
 

Let us parse the input "fields" string into a collection of strings named "word". Each string represents the name of a field which is needed in the resulting JSON:

            //parse fields string
            string[] words;
            string[] separators = { ",", ".", "!", "?", ";", ":", " " };

            words = fields.Split(separators, StringSplitOptions.RemoveEmptyEntries);
We now have a collection of field names which will match properties on our POCO object.

So let's filter the properties of our object based on that collection of field names
To do that, we need to know what the properties of an object are, which can be easily achieved through reflection:

            Type t = o.GetType();

            string str = "";

            foreach (string f in words)
            {
                try
                {
                    //get the corresponding value from the array
                    PropertyInfo p = t.GetProperty(f, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.InstObjeance);
This gets us the value of each property specified inthe "fields" string. Now we need somewhere to put these values. Fortunately, even though C# is a strongly typed language, the framework makes provision for a dynamically typed object, constructed at runtime. In the .NET framework, this object has the exotic name of an "ExpandObject"; an object which can be contructed programatically. 

We initialise the ExpandoObject as a dictionary of name, value pairs:

            var exp = new ExpandoObject() as IDictionary<string, Object>;

Then let's put the reflected value from the original object, into our ExpandoObject, iterate through all the fields then return the dynamic ExpandoObject as JSON:

           foreach (string f in words)
            {
                try
                {
                    //get the corresponding value from the array
                    PropertyInfo p = t.GetProperty(f, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

                    str = p.GetValue(o).ToString();

                    //add property to dynamic object
                    exp.Add(f, str);
                }
                catch(Exception e)
                {
                    FieldNotFoundException ex = new FieldNotFoundException("The Field named " + f + " Does not exist in this object",e);
                    throw ex;
                }
            }
            //return the dynamic object
            return exp;
 

Now with our query of:
/api/Students?fields=lastName,firstMidName

We get a return of:

[
{
lastName: "Alexander",
firstMidName: "Carson"
},
{
lastName: "Alonso",
firstMidName: "Meredith"
},
{
lastName: "Anand",
firstMidName: "Arturo"
}
]

Code for this sample can be messaged by emailing [email protected]

Roland Kamsika 2017

Total: 0 Comment(s)