Tuesday, August 10, 2010

Using Java reflection to render a JSON feed

In today’s post we are going to look at a method for rendering JSON output from our java web app. It’s important to note that what I’m going to show relies on a couple of things:

1: I like to use Java beans to encapsulate data between elements of the MVC model. If more than one element is needed the beans are added to a list.

2: These beans use standard accessor methods starting with “get” to access elements private methods.

3: Our controller, a servlet , adds the beans to the request via by setting a attribute and then forwards to the view (usually a jsp page) using a requestdispatcher.

If you look at the code at github you’ll see that we have 4 of these bean stores with different accessor methods and private variables. And here in lies the problem. We could write a JSON encoder that worked on each bean but that’s going to be duplicating a lot of work. The answer is to use Java Reflection to “look” into the bean and extract it’s accessor methods. This will allow us to write one servlet that can process any bean or list of beans and convert it to JSON notation.

For more information on reflection look at Java Reflection

We will use the JSON library from JSON.org to make life easy.

Our Servlet that will generate the JSON will not know what type of bean is being sent to it, or if it is a list of beans. So rather than deal with a distint class we just deal with the Object class. We can get the beans and test for a list like this:

Object temp=request.getAttribute("Data");
Class c = temp.getClass();
String className=c.getName();
if (className.compareTo("java.util.LinkedList")==0){

If it’s not a linked list we will want to get the Object, find all the methods and call the accessor methods. Here’s our method for doing this which returns a JSONObject with only values from the bean that actually have data. In this example “Value” has been obtained from the request using getAttribute.

private JSONObject  ProcessObject(Object Value){ //Value has been passed to the servlet
 JSONObject Record=new JSONObject();
  
 try {
            Class c = Value.getClass();
            Method methlist[] = c.getDeclaredMethods();
            for (int i = 0; i < methlist.length; i++) {  
              Method m = methlist[i];
             
              String mName=m.getName();
             
                 if (mName.startsWith("get")==true){
                       String Name=mName.replaceFirst("get", "");
                  Class partypes[] = new Class[0];
                  Method meth = c.getMethod(mName, partypes);
                 
                  Object rt= meth.invoke(Value);
                  if (rt!=null){
                   System.out.println(Name+" Return "+ rt);
                   try{
                    Record.put(Name,rt);
                   }catch (Exception JSONet){
                 System.out.println("JSON Fault"+ JSONet);
                 return null;
                }
               
                  }
                 }
            }
            
            
         }
         catch (Throwable e) {
            System.err.println(e);
         }
         return Record;
 }
Dealing with a linked list of objects is just a case of iterating through the list and calling the above object for each bean we want to encode:
if (className.compareTo("java.util.LinkedList")==0){ //Deal with a linked list
 List Data = (List)request.getAttribute("Data"); 
 Iterator iterator;
 JSONObject JSONObj=new JSONObject();
 JSONArray Parts=new JSONArray();
 iterator = Data.iterator();     
 while (iterator.hasNext()){
  Object Value=iterator.next();
  JSONObject obj =ProcessObject(Value);
  try {
   Parts.put(obj);
  }catch (Exception JSONet){
           System.out.println("JSON Fault"+ JSONet);
          }
 }
 try{
  JSONObj.put("Data",Parts);
 }catch (Exception JSONet){
       System.out.println("JSON Fault"+ JSONet);
      }
 if (JSONObj!=null){
  PrintWriter out = response.getWriter();
  out.print(JSONObj);
 } 
Our simple code could be extended. Although it will deal with simple types stored in beans (strings, ints, longs, dates etc) if your bean stores more complex data (arrays or lists) then handling the reflection will need to be a lot more complicated!

1 comment: