This is the second part to my Java Reflection Library series. In this post I will discuss how to get information about arrays and then use this as well as the information from part 1 of this series to create a generic class json serializer. Basically I want to be able to convert any class into it’s corresponding json form.
The material for this post was heavily influenced by Michael Pogrebinsky and his awesome course on Udemy. If you want to learn more about the Java reflection library I highly recommend his course on the subject. It’s a little expensive but you can find coupons online to reduce the price to around $15. Anyways, let’s get started.
- First of all, it’s important to consider all the types of values we want to be able to represent as json. I think there are three main types we should consider. The first are primitives. These will be easy to add by simply using their built-in toString() method. The second are Strings. These also will be easy (obviously). And the third are arrays. Arrays are also pretty simple to convert once you determine the type of values it contains.
- If you have an array of unknown object type, you can get information from it using this method:
// arrObject is the Object array Class<?> arrType = arrObject.getClass().getComponentType(); int size = Array.getLength(arrObject); // Loop over the array and inspect each element for (int i = 0; i < size; i++) { Object item = Array.get(arrObject, i); if (arrType.isPrimitive()) { .....
- If you have an array of unknown object type, you can get information from it using this method:
- The idea is simple. We want to take in a class object, for each field we determine the type, then create the json text for it. This json creater class will have a recursive method which checks for the class fields. If there is a field that contains another class, it will recursively nest the objects creating clean json text.
- A good way to keep track of the text as we move over an objects fields is to use a StringBuilder. This will allow us to easily append more text to the end as we gather more information.
- So my JsonSerializer class looks like this so far:
public class JsonSerializer { private final Object object; private final int indent; private String json; public JsonSerializer(Object object, int indent) throws IllegalAccessException { this.object = object; this.indent = indent; this.json = createJson(this.object, this.indent); System.out.println(this.json); } public String getJson() { if (this.json != null) return this.json; else throw new NullPointerException("No JSON Object Found"); } }
- So my JsonSerializer class looks like this so far:
- First of all we will need to create a loop. This will looper over the object’s fields and determine their type. Then it will parse the field and value accordingly into json. So go ahead and make a new method called
createJson()
. This will take the object we want to convert to json as well as the amount we want to indent.- Now we need to setup the StringBuilder and get the fields of the given object. So this is what the createJson method looks like now.
private static String createJson(Object object, int indent) throws IllegalAccessException { Field[] fields = object.getClass().getDeclaredFields(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(spacing(indent)); stringBuilder.append("{"); stringBuilder.append("\n"); int length = Array.getLength(fields); }
- Now we need to setup the StringBuilder and get the fields of the given object. So this is what the createJson method looks like now.
- Before we get too far, you will want to go ahead and add the method called
spacing(int indent)
. This is what will keep track of how much indentation each line gets. And we also want to use a method calledformatString(String value)
which will properly convert our values into strings.- These methods are simple to create.
private static String formatString(String value) { return String.format("\"%s\"", value); } private static String spacing(int indent) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < indent; i++) { stringBuilder.append("\t"); } return stringBuilder.toString(); }
- These methods are simple to create.
- Ok, now back to the createJson method. We start with a loop that increments up for every field in the object. At each increment we’ll instantiate the current field so it can be analyzed. If there is a synthetic field in the list we will just ignore it for now. There are 5 main parts to the if statements in the loop. We want to check for primitives, strings, arrays, synthetic fields, and nested objects.
- This part of the loop looks like this:
for (int i = 0; i < length; i++) { Field field = fields[i]; field.setAccessible(true); if (field.isSynthetic()){continue;} stringBuilder.append(spacing(indent + 1) + formatString(field.getName()) + ": "); if (field.getType().isPrimitive()) { stringBuilder.append(formatString(field.get(object).toString())); } else if (field.getType().equals(String.class)) { stringBuilder.append(formatString(field.get(object).toString())); } else if (field.getType().isArray()) { stringBuilder.append(jsonArray(field.get(object), indent)); } else { stringBuilder.append(createJson(field.get(object), indent)); } if (i != length - 1) {stringBuilder.append(",");} stringBuilder.append("\n"); }
- The end of the method will just close off the curly braces and return the complete StringBuilder string. It’s pretty simple and will look something like this:
stringBuilder.append(spacing(indent)); stringBuilder.append("}"); return stringBuilder.toString();
- This part of the loop looks like this:
- The last thing to add is the jsonArray method. This method will convert an array into json text. The method itself is very similar to the createJson method. Except it uses square braces and accesses values using
Object item = Array.get(arrayObject, index);
.- The full array to json method looks like this.
private static String jsonArray(Object arrObject, int indent) throws IllegalAccessException { Class<?> arrType = arrObject.getClass().getComponentType(); int size = Array.getLength(arrObject); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(spacing(indent + 1)); stringBuilder.append("[\n"); for (int i = 0; i < size; i++) { Object item = Array.get(arrObject, i); if (arrType.isPrimitive()) { stringBuilder.append(spacing(indent + 1)); stringBuilder.append(formatString(item.toString())); } else if (arrType.equals(String.class)) { stringBuilder.append(spacing(indent + 1)); stringBuilder.append(formatString(item.toString())); } else { stringBuilder.append(createJson(item, indent + 1)); } if (i != size - 1) {stringBuilder.append(",");} stringBuilder.append("\n"); } stringBuilder.append(spacing(indent)); stringBuilder.append("]"); return stringBuilder.toString(); }
- The full array to json method looks like this.
- And that’s pretty much it. This is the entire program if you are interested:
package jsonwriter; import java.lang.reflect.Array; import java.lang.reflect.Field; public class JsonSerializer { private final Object object; private final int indent; private String json; public JsonSerializer(Object object, int indent) throws IllegalAccessException { this.object = object; this.indent = indent; this.json = createJson(this.object, this.indent); System.out.println(this.json); } public String getJson() { if (this.json != null) return this.json; else throw new NullPointerException("No JSON Object Found"); } private static String createJson(Object object, int indent) throws IllegalAccessException { Field[] fields = object.getClass().getDeclaredFields(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(spacing(indent)); stringBuilder.append("{"); stringBuilder.append("\n"); int length = Array.getLength(fields); for (int i = 0; i < length; i++) { Field field = fields[i]; field.setAccessible(true); if (field.isSynthetic()){continue;} stringBuilder.append(spacing(indent + 1) + formatString(field.getName()) + ": "); if (field.getType().isPrimitive()) { stringBuilder.append(formatString(field.get(object).toString())); } else if (field.getType().equals(String.class)) { stringBuilder.append(formatString(field.get(object).toString())); } else if (field.getType().isArray()) { stringBuilder.append(jsonArray(field.get(object), indent)); } else { stringBuilder.append(createJson(field.get(object), indent)); } if (i != length - 1) {stringBuilder.append(",");} stringBuilder.append("\n"); } stringBuilder.append(spacing(indent)); stringBuilder.append("}"); return stringBuilder.toString(); } private static String jsonArray(Object arrObject, int indent) throws IllegalAccessException { Class<?> arrType = arrObject.getClass().getComponentType(); int size = Array.getLength(arrObject); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(spacing(indent + 1)); stringBuilder.append("[\n"); for (int i = 0; i < size; i++) { Object item = Array.get(arrObject, i); if (arrType.isPrimitive()) { stringBuilder.append(spacing(indent + 1)); stringBuilder.append(formatString(item.toString())); } else if (arrType.equals(String.class)) { stringBuilder.append(spacing(indent + 1)); stringBuilder.append(formatString(item.toString())); } else { stringBuilder.append(createJson(item, indent + 1)); } if (i != size - 1) {stringBuilder.append(",");} stringBuilder.append("\n"); } stringBuilder.append(spacing(indent)); stringBuilder.append("]"); return stringBuilder.toString(); } private static String formatString(String value) { return String.format("\"%s\"", value); } private static String spacing(int indent) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < indent; i++) { stringBuilder.append("\t"); } return stringBuilder.toString(); } }
- There is still lots of room for improvement. I think some of the spacing for the square brackets is still a little off. So if you want to take this and improve it feel free!
Thank you so much for reading this!
-Kyle