Reflection and JSON parsing

One of the things I most hate to do in programming is parsing JSON. Not because it’s hard, but because it’s trivial. That’s the problem- it’s boring yet very time consuming. I don’t want to have to write the line myvar = jsonValue.getInt(“myvarname”,0); 1000 times. I want to take a string and just suck it up into an object. Nowdays in Java I would just use GSON, a library by Google that uses reflection to do this and is used widely by the Java community. But there was a time before this library became popular and I actually had the same idea. Since I rarely used reflection (and still rarely use it, I find it’s generally a code smell) I thought it would be a great opportunity to learn. So here it is- my own personal JSON parsing library.

As an aside- I had originally planned on revisiting this code and expanding it for a part 2 on this, but I’ve decided there’s no value here over using GSON. So I’ll take some of the features I planned to add and use them elsewhere in the future, in a bit of a broader way. As a result this update is going to be lower in detail and more in code, since I have no interest in keeping this library updated and will be moving my libraries that depend on it to GSON. I’m posting this mainly to increase my ready to go post count and to provide an interesting example into using reflection.

The point of the library was that I wanted to be able to pass it a string and an object type and have it parse it into an object of that type. It should of course be fully recursive and parse any sub-objects as well, and to be able to handle arrays. It should also be able to reverse the process and turn an object back into JSON. It does this by using reflection to iterate over the fields of the object and parsing each one with the proper annotation (so we can have fields in the object which aren’t parsed). In order to do this there has to be a zero parameter constructor. Basically it works exactly like GSON but I chose the opposite default on whether or not to require an annotation. Here’s the base code:

JSONField.java

package com.gabesechan.android.reusable.json;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface JSONField {
	boolean optional() default false;	
}

This declares the annotation I require on fields. It has a single value, optional. By default any field that’s set to be read in is expected to be in the JSON. If it isn’t, we fail. Setting the optional=true field on the annotation will leave the value unset if it isn’t in the JSON.

One thing you’ll note in this code- I only made it work for the class versions of each type. This is a limitation of what I needed to code, not of Java. If I was continuing to support this, I would fix that. You’ll see that the library checks for the type of the field versus one of the expected types, and parses it appropriately. Any type that isn’t known is handled recursively as a JSON object.


	public static JSONObject toJSON(Object input) throws IllegalAccessException, JSONException, InstantiationException{
		JSONObject results= new JSONObject();
		Class inputClass = input.getClass();
		Field fields[] = inputClass.getFields();
		
		for(Field curField : fields){
			String fieldName = curField.getName();
			JSONField annotation = (JSONField)curField.getAnnotation(JSONField.class);
			if(annotation == null){
				continue;
			}
			if(curField.getType().isArray()){
				//First, check if this is an optional field that doesn't exist.  If so, ignore it
				if(annotation.optional() && curField.get(input) == null){
					continue;
				}

				//Handling an array requires us to allocate the array, stuff each index into the array, and set the results at the end
				JSONArray jsonArray = new JSONArray();
				Object javaArray[] = (Object[])curField.get(input);
				for(Object curObject : javaArray){
					jsonArray.put(toJSONArrayIndex(curObject));
				}
				results.put(fieldName,  jsonArray);
			}
			else{
				Object fieldResult = toJSONField(input, curField);
				if(fieldResult != null){
					results.put(fieldName, fieldResult);
				}
			}
		}
		
		return results;
	}
	
    //Helper function to convert tp a single JSON field	
	private static Object toJSONField(Object input, Field curField) throws IllegalAccessException, JSONException, InstantiationException {
		String fieldType = curField.getType().getName();
		
		//See if this is annotated as an optional member.  If it is, return NULL if it does not exist
		//Otherwise the use of getXXX below will throw an exception on non-existance
		JSONField annotation = (JSONField)curField.getAnnotation(JSONField.class);
		if(annotation == null){
			return null;
		}
		if(annotation.optional() && curField.get(input) == null){
			return null;
		}
		if(fieldType.equals("java.lang.Boolean") || fieldType.equals("java.lang.Double") || fieldType.equals("java.lang.Integer") ||
				fieldType.equals("java.lang.Long") || fieldType.equals("java.lang.String")){
			return curField.get(input);
		}
		else{
			//Not a java primitive.  Run this recursively.
			Object fieldObject = curField.get(input);
			return toJSON(fieldObject);
		}
		
		
	}
	
	//Helper function to convert to a single index in a JSON array
	private static Object toJSONArrayIndex(Object curObject) throws JSONException, IllegalAccessException, InstantiationException{
		Class baseType = curObject.getClass();
		String baseTypeName = baseType.getName();
		if(baseTypeName.equals("java.lang.Boolean") || baseTypeName.equals("java.lang.Double") || baseTypeName.equals("java.lang.Integer") ||
				baseTypeName.equals("java.lang.Long") || baseTypeName.equals("java.lang.String")){
			return curObject;
		}
		else{
			//Not a JSON primitive.  Run this recursively.
			return toJSON(curObject);
		}
		
		
	}

This Post Has Been Viewed 5,435 Times

Leave a Reply