Skip to main content

Java JSON Serialization

    There are already a lot of JSON libraries out there you can use to convert your Entities to and from JSON Objects, such as, the Jackson library. However, I usually use the org.json library (this is also the JSON library used in Android) and for each Object create "toJSON" and "fromJSON" methods where I have a long and complex process of setting and retrieving values dependent on their particular implementation. This can be tedious, verbose, and time consuming. So, I wanted to hack up a simply alternative myself rather than using a third party library (more fun that way!). And here's the code:

JSONSerializer (class that actually performs the logic of converting objects):

/**
 * Created by chRyNaN on 1/9/2016. Untested as of now and doesn't work for some specific and more complex use cases.
 */
public class JSONSerializer {

    public static <T> JSONObject toJSON(T obj){
        JSONObject json = new JSONObject();
        JSONArray jArray = new JSONArray();
        JSONObject o;
        Class<T> clazz = (Class<T>) obj.getClass();
        for (Field f : clazz.getDeclaredFields()) {
            try {
                f.setAccessible(true);
                Object property = f.get(obj);
                if (property instanceof Map<?, ?>) {
                    //Create JSONArray of JSONObjects
                    for(Map.Entry<?, ?> e : ((Map<?, ?>) property).entrySet()){
                        o = new JSONObject();
                        o.put("key", e.getKey());
                        o.put("value", e.getValue());
                        jArray.put(o);
                    }
                    json.put(f.getName(), jArray);
                }else if (property instanceof Collection<?>){
                    for(Iterator<?> i = ((Collection<?>) property).iterator(); i.hasNext();){
                        jArray.put(i.next());
                    }
                    json.put(f.getName(), jArray);
                }else if (property.getClass().isArray()){
                    Object[] oArray = toUseableArray(property);
                    for(int i = 0; i < oArray.length; i++){
                        jArray.put(oArray[i]);
                    }
                    json.put(f.getName(), jArray);
                }else {
                    json.put(f.getName(), property);
                }
            } catch (NullPointerException npe) {
                npe.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return json;
    }

    public static <T> T fromJSON(JSONObject obj, T entity){
        if(obj != null){
            Class<T> clazz = (Class<T>) entity.getClass();
            JSONArray jArray;
            JSONObject o;
            for(Iterator<String> it = obj.keys(); it.hasNext();){
                try {
                    String s = it.next();
                    Field f = clazz.getDeclaredField(s);
                    f.setAccessible(true);
                    Class<?> type = f.getType();
                    if(type.isAssignableFrom(Map.class)){
                        Map<Object, Object> map = new HashMap<>();
                        jArray = obj.getJSONArray(s);
                        for(int i = 0; i < jArray.length(); i++){
                            o = jArray.getJSONObject(i);
                            map.put(o.get("key"), o.get("value"));
                        }
                        f.set(entity, map);
                    }else if(type.isAssignableFrom(Collection.class)){
                        Collection<Object> collection = getCollection(type);
                        jArray = obj.getJSONArray(s);
                        for(int i = 0; i < jArray.length(); i++){
                            collection.add(jArray.get(i));
                        }
                        f.set(entity, collection);
                    }else if(type.isArray()){
                        //TODO uh-oh what about unboxing back into primitive types?
                        jArray = obj.getJSONArray(s);
                        Object[] array = new Object[s.length()];
                        for(int i = 0; i < jArray.length(); i++){
                            array[i] = jArray.get(i);
                        }
                        f.set(entity, array);
                    }else{
                        f.set(entity, obj.get(s));
                    }
                } catch (NoSuchFieldException ne) {
                    ne.printStackTrace();
                } catch (JSONException je){
                    je.printStackTrace();
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return entity;
    }

    private static Collection<Object> getCollection(Class<?> type){
        Collection<Object> c = new ArrayList<>();
        try{
            if(type.isAssignableFrom(List.class)){
                c = new ArrayList<>();
            }else if(type.isAssignableFrom(Set.class)){
                c = new HashSet<>();
            }else if(type.isAssignableFrom(Queue.class)){
                c = new PriorityQueue<>();
            }else if(type.isAssignableFrom(Deque.class)){
                c = new ArrayDeque<>();
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return c;
    }

    /*
     * If you have an Object (of unknown type) which is an array, you can simply iterate through the array after
     * casting it to an array object (Object[]), but this ignores primitive types. Primitive types must first
     * be boxed to their corresponding objects types.
     */
    private static Object[] toUseableArray(Object obj){
        //make sure the object is an array
        if(obj.getClass().isArray()){
            if(obj instanceof int[]){
                return toBoxedArray(obj);
            }else if(obj instanceof double[]){
                return toBoxedArray(obj);
            }else if(obj instanceof float[]){
                return toBoxedArray(obj);
            }else if(obj instanceof short[]){
                return toBoxedArray(obj);
            }else if(obj instanceof char[]){
                return toBoxedArray(obj);
            }else if(obj instanceof byte[]){
                return toBoxedArray(obj);
            }else if(obj instanceof long[]){
                return toBoxedArray(obj);
            }else if(obj instanceof boolean[]){
                return toBoxedArray(obj);
            }else{
                //already is an Object array so cast and return it
                return (Object[]) obj;
            }
        }
        return null;
    }

    /*
     * Generic method to box primitive arrays to their Object counter parts.
     * Referenced this answer on StackOverflow: http://stackoverflow.com/a/3775583/1478764
     * Return type must be known when calling this method.
     */
    private static <T> T[] toBoxedArray(Object obj){
        if(obj == null) {
            throw new NullPointerException("Null values are not supported");
        }
        final Class<?> cls = obj.getClass();
        if(!cls.isArray() || !cls.getComponentType().isPrimitive()) {
            throw new IllegalArgumentException("Only primitive arrays are supported");
        }
        final int length = Array.getLength(obj);
        if(length==0) {
            throw new IllegalArgumentException("Only non-empty primitive arrays are supported");
        }
        T[] arr = null;
        for (int i = 0; i < length; i++) {
            final Object wrapped = Array.get(obj, i);
            if(arr == null){
                arr = (T[]) Array.newInstance(wrapped.getClass(), length);
            }
            arr[i] = (T) wrapped;
        }
        return arr;
    }

}

JSONableObject (superclass for any Object wanting to be converted):

/**
 * Created by chRyNaN on 1/9/2016. I Wanted this to be an interface that has default methods, that way
 * you could simply implement the interface in your class without having to provide code or being stuck
 * in a single hierarchy. Unfortunately, Android doesn't support default methods (or many features of Java SE 8).
 * If you would like to use this in a Java SE 8 environment, it would be better to change this to an interface
 * (replace "abstract class" with "interface") and add the "default" qualifier to the beginning of the methods.
 */
public abstract class JSONableObject {

    JSONObject toJSON(){
        return JSONSerializer.toJSON(this);
    }

    String toJSONString(){
        JSONObject obj = this.toJSON();
        if(obj != null){
            return obj.toString();
        }
        return this.toString();
    }

    void fromJSON(JSONObject obj){
        JSONSerializer.fromJSON(obj, this);
    }

}

In a perfect world where Android supported Java 8 default methods (can use the following class in any full Java 8 environment), JSONable:

/**
 * Created by chRyNaN on 1/9/2016. Java SE 8 compatible Interface. Simply implement this interface in your custom
 * class and your Object is able to be converted to and from a JSONObject.
 */
public interface JSONable {

    default JSONObject toJSON(){
        return JSONSerializer.toJSON(this);
    }

    default String toJSONString(){
        JSONObject obj = this.toJSON();
        if(obj != null){
            return obj.toString();
        }
        return this.toString();
    }

    default void fromJSON(JSONObject obj){
        JSONSerializer.fromJSON(obj, this);
    }

}

You can also view the classes in this Gist.

Comments

Popular posts from this blog

Face detection and live filters

Live video filters are becoming a popular trend fueled by Facebook (through their purchase of Msqrd) and Snapchat incorporating the features into their apps. These filters apply images or animations to your face using face tracking software. This technology has been around for awhile but is becoming increasingly more common due to the powerful CPU's that our mobile phones now have. Google provides an API that provides face tracking abilities through the Google Play Services library called Mobile Vision. I'm going to use their API to build a basic live filter app. The end result will look something like this:


    The bounding box wraps around the detected face and the sunglasses are the filter I chose (which is just a PNG image) which are drawn over the eyes. You could use any PNG image (with alpha for the background) you want, you will just have to adjust the layout according to where the image should be displayed. As you move your head, the box and sunglasses are redrawn…

Setting Up Connection Pooling With Elastic Beanstalk

Amazon's Elastic Beanstalk is a service which automatically scales your application when needed. It uses Amazon's Elastic Compute Cloud (EC2) instances as deployable containers which when your app requires more resources more containers will be deployed. This removes the need to manually configure your EC2 instance whenever you need more connections or resources and attempts to add simplicity to the maintenance aspect of your application. So, when you get more users of your app, your app will scale accordingly.

    Unfortunately, along with the ability to scale automatically, comes less control and configuration. Things you would normally have the ability to configure to your liking, such as your server, you no longer can. Amazon attempts to address this issue with configuration files. You can provide configuration files which can set up your server. These files are either written in JSON or the horrible format YAML. Though these files provide some level of control, you ca…

Android Guitar Tuner

Recently I created a guitar tuner application for Android that is written with pure Java (no C++ or NDK usage). The design was inspired by the Google Chrome team's guitar tuner web app using the WebAudio API. I wanted to code a version written natively for Android that didn't have to rely on a WebView, the WebAudio APIs, or server-side logic. Also, I wanted this application to be available to as many versions of Android as possible (whereas the WebAudio API may only be supported in more recent versions of WebView available only on newer flavors of Android). So, I coded it from scratch. I used a portion of the open source TarsosDSP project (their YIN algorithm) to help with the pitch detection.

    The application is available in the Google Play Store for Android: https://play.google.com/store/apps/details?id=com.chrynan.guitartuner. The project is completely open source and the code can be found on the GitHub repository: https://github.com/chRyNaN/Android-Guitar-Tuner. Fi…