OhkaBaka OhkaBaka - 26 days ago 20
Java Question

Dynamic JSON structure to Java structure

I'm working on a project to use JSON as a configuration framework for creating Java objects. This is also my first professional Java project coming from years of experience in CF/PHP/JS etc...

EVERY resource I can find on converting JSON to Java is predicated on the idea that you have to manually build the object in Java first, a POJO, and then use the JSON to populate it.

As a web language veteran, I'm choking on this idea. I get that compiled languages play differently, but I though it was a tenet developers from command line to machine language shared: "If you have to do it more than twice, automate it."

...and yet it seems like the presiding Java wisdom is: Do the tedious structure building by hand, every time, then use GSON/Jackson/whatever to populate the rigid structure. If your JSON changes (and it will, because JSON) do the whole thing over again.

Is there a parallel to the web code way of thinking?

1) Load JSON

2) Build native language structure based on JSON structure.

3) Populate new object with data from structure.

4) Use object however you wish.

(As @Thomas pointed out, the most accurate scenario for why this would be necessary is a API response that returns JSON, it might be different periodically, and while duck typing is generally uncomfortable for high level languages, even converting EVERYTHING to a string saves effort and time compared to rebuilding your framework every time {or having a tool that converts your JSON from myriad sources to be usable by any other tool}. Again, it seems obvious from the perspective of cowboy languages, but so foreign to the high level languages people aren't understanding the question.)

Answer

I read carefully the comments above and I think there is a very common scenario where an ad hoc Java object would be extremely useful even if the API structure is known at compile time.

Lets say you deal with an API that return a complex JSON object in which you are only interested in a very specific property value.

Take as an example the folowing google map API call to get geo details givin an address:

https://maps.googleapis.com/maps/api/geocode/json?address=sunnyvale,CA

As you can check and see, the response structure is quite complex. Assuming you only want to get the lat and lng properties value, there should be an easier way rather than predefine the complete corresponding Java class.

So yes, if you don't want to go with the strongly typed predefined Java class, you will need to use raw Object type and Maps. More specifically, a StringMap.

And now finally I can give a solution:

Ideally, you would like to access the properties with the same native notation like you would do in JS. Something like this:

String url = "https://maps.googleapis.com/maps/api/geocode/json?address=" + address;
String responseStr = fetch(url);
JsonHelper response =  JsonHelper.forString(responseStr);

String status = (String) response.getValue("status");
if(status != null && status.equals("OK")) {
   lat = (Double) response.getValue("results[0].geometry.location.lat");        
   lng = (Double) response.getValue("results[0].geometry.location.lng");
}

The following JsonHelper class code is taken from the jello-framework and it lets you do exactly that.

package jello.common;

import java.util.List;

import com.google.gson.Gson;
import java.util.AbstractMap;

public class JsonHelper {

    private Object json;

    public JsonHelper(String jsonString) {
        Gson g = new Gson();
        json = g.fromJson(jsonString, Object.class);
    }

    public static JsonHelper forString(String jsonString) {
        return new JsonHelper(jsonString);
    }

    @SuppressWarnings("unchecked")
    public Object getValue(String path) {
        Object value = json;
        String [] elements = path.split("\\.");
        for(String element : elements) {
            String ename = element.split("\\[")[0];

            if(AbstractMap.class.isAssignableFrom(value.getClass())) {
                value = ( (AbstractMap<String, Object>) value).get(ename);

                if(element.contains("[")) {
                    if(List.class.isAssignableFrom(value.getClass())) {
                        Integer index = Integer.valueOf(element.substring(element.indexOf("[")+1, element.indexOf("]")) );
                        value = ((List<Object>) value).get(index);
                    }
                    else {
                        return null;
                    }
                }
            }
            else {
                return null;
            }
        }

        return value;
    }
}
Comments