Press "Enter" to skip to content

Using JSON with StringTemplate

I looked at a few of the Java libraries for JSON and finally choose org.json. It’s small and does just what I need it to, no more, no less. Well a little less. See, I decided to replace some of my existing value objects with JSON objects. (They were going to need to be serialized to/from JSON format anyway.) I am using StringTemplate (ST) to generate the HTML presentation so now my templates will need to get data from the JSON objects. The trouble is that the org.json implementation is not ST friendly.

ST knows how to get data from scalar types like String and Integer, and from object properties, Maps, and Collections. JSONObject wraps a HashMap but does not implement Map or extend HashMap so ST cannot get at the items it holds. I could have modified ST to treat JSONObject and JSONArray special but there is no reason for ST to know anything about these specific classes. The simple thing to do is have JSONObject implement Map and this is what I did. Most of the methods are a trivial call to the underlying HashMap method. A few methods must go through existing JSONObject methods to maintain the intended semantics. For example, get(Object key) calls opt(String key) because get should return null if the key is not found.

A similar thing was done for JSONArray. I made it implement Collection and Iterable. Since JSONArray has a put(Collection) method and JSONObject has a put(String, Map) method this caused infinite recursion because JSONArray is now a Collection and JSONObject is a Map. The solution was to add more specific methods put(JSONArray) and put(String, JSONObject) so that JSONObjects and JSONArrays can be added to each other.

There is one more problem to solve. Sometimes I need to access the contents of the JSON object structure and sometimes I need to serialize it as a JSON string (after all, that’s what JSON is for). JSONObject has a toString method which ST will generally use but not if the object is a Map. Once ST sees an object is a Map it will not use it any other way. That means you can’t call toString on it or access any other properties. This is a good thing in general but I sill need the JSON string. I could have the controller call toString and stuff the result into another ST attribute but that puts more burden on the controller and requires that the controller know if the presentation is going to need the JSON string. I decided to create a pseudo key called “JSONString”. In the get method I check if the key is equal to “JSONString” and if it is I return the output of toString. Normally this would be a bad idea but in this case I think it is the best option. Note: ST already pollutes the name space of keys. In ST $mymap.keys$ returns a collection of all the key names and not the map item with key “keys”. It would be nice if $mymap.(“keys”)$ gave you the map item but it doesn’t. I think of both JSONString and keys as a hack but I don’t have a better way right now. ST has no problem with additional properties on Collections so I was able to add a getJSONString method to JSONArray.

Examples of use

this code

JSONObject jo = new JSONObject();
jo.put("long", 4);
jo.put("string", "some string");
JSONArray ja = new JSONArray();
ja.put("one");
ja.put("two");
ja.put("three");
jo.put("array", ja);

StringTemplate st = new StringTemplate(
"l=$jo.long$ s=$jo.string$n" +
"keys: $jo.keys : {[$it$] }$n" +
"array: $jo.array;separator=\", \"$n" +
"json=$jo.JSONString$n" +
"json array=$jo.array.JSONString$n");
st.setAttribute("jo", jo);
String text = st.toString();

produces this output:

l=4 s=some string
keys: [string] [array] [long]
array: one, two, three
json={"string":"some string","array":["one","two","three"],"long":4}
json array=["one","two","three"]

What I did with JSON is an example of a general principle of using ST: Wrap (or change) your existing object structures in a way that ST already deals with rather than change ST to understand your data.

I can make my changes to org.json available if anyone is interested.

6 Comments

  1. I82Much
    I82Much Friday, January 8, 2010

    Looks like I figured it out on my own.

    Let $vars$ be the map of key value pairs you want to iterate over. Then all you need to do is

    $vars.keys, vars.values: { k, v | $k$ : $v$ }$

    Awesome!

    Thanks for the amazing tool.

  2. I82Much
    I82Much Thursday, January 7, 2010

    Hi John,

    I’m very interested in this project, and am attempting to use it to do some code generation.

    My question is, is it possible to have lists of key value pairs and access them via st?

    For instance,

    {
    “variables”:[
    {
    “type”:”int”,
    “name”:”Hello”,
    “enumeratedValues”:{
    “TEST”:0,
    “TEST2”:1,
    “TEST3”:2
    }
    },
    {
    “type”:”double”,
    “name”:”PI”
    }
    ]
    }

    I want to be able to loop over all of my variables, and if they have a set of enumerated values, access them and print that set of key/value pairs. I can’t figure out how to do this. Any tips?

    Thanks,

    Nick

  3. Jeyaram
    Jeyaram Monday, June 9, 2008

    Thank you very much for sharing it.

    FYI, I have tried using Json-Lib http://json-lib.sourceforge.net/ that implements Map and other collection interfaces. It seems to be working fine with ST but depends on more libraries.

    Regards
    Jeyaram

  4. John Snyders
    John Snyders Monday, June 9, 2008

    Hi Jeyarm, I added a link to the source zip to the main page. JSON for ST

    Enjoy

  5. Jeyaram
    Jeyaram Thursday, June 5, 2008

    Dear John,

    I just came across your blog when looking for using JSON with StringTemplate.

    Would it be possible for you to share your changes to org.json with me?

    Thank you

    Regards
    Jeyaram

Comments are closed.