The Source for Java Technology Collaboration
User: Password:



Rex Young

Rex Young's Blog

How to bind front-end JSON strings to back-end Spring beans?

Posted by rexyoung on November 20, 2008 at 10:11 PM | Comments (1)

It is very common that you use a lot of JSON on the front-end and use Spring Web MVC on the back-end. JSON strings are usually sent to back-end as parameters in the HTTP requests. As a back-end guy, you might hate to maintain two set of value objects JSON objects and Spring beans.

I encountered such a situation once about half a year ago. I solved it by convert JSON style parameters to Spring style parameters before they reach any meaningful Spring parts e.g. controllers, forms, commands, etc. Thus Spring directly bind them to beans and commands. As a result, both Spring and your back-end are not aware of JSON.

This was a very tiny issue. I finished it and forgot it. I was recently asked about this issue again by a friend of mine. He took a look at my code and thought it is useful.

Then I googled keywords like "bind JSON strings to Spring beans" and found that lots of links are talking about Views e.g. Spring Json View. There were many techniques for outgoing conversions e.g. JsonLigTag but very few for incoming conversions. So I think it is worth to write it down and share with people.

JSON style paramters in HTTP request:
    KEY            VALUE
    ---            -----
    employee       {firstName:"F",lastName:"L",address:["street","town","state"],ssn:123456789}

Spring style parameters in HTTP request:
    KEY                    VALUE
    ---                    -----
    employee.firstName     F
    employee.lastName      L
    employee.address[0]    street
    employee.address[1]    town
    employee.address[2]    state
    employee.ssn           123456789

There are three classes in my solution. The first class JsonAwareDispatcherServlet is used to replace Spring DispatcherServlet which usually is the first servlet the HTTP requests reach. The new servlet checks whether the HTTP request contains any JSON strings. If not, resume the Spring logical as usual. If yes, convert them, then also resume the Spring logical as usual.

The second class JsonAwareHttpServletRequest is used to hold newly converted parameters. The third class is JsonBinderUtils which has all solid utility methods for the conversion.

I am posting source code here as they are short classes. Hope it helps.

JsonAwareDispatcherServlet
public class JsonAwareDispatcherServlet extends DispatcherServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        if (JsonAwareHttpServletRequest.jsonInside(request)) {
            super.doService(new JsonAwareHttpServletRequest(request), response);
        } else {
            super.doService(request, response);
        }
    }
}
JsonAwareHttpServletRequest
public class JsonAwareHttpServletRequest extends HttpServletRequestWrapper {

    private Map springParamMap;

    public JsonAwareHttpServletRequest(HttpServletRequest request) {
        super(request);

        springParamMap = convertJsonParameters(request.getParameterMap());
    }

    @Override
    public String getParameter(String name) {
        String[] values = springParamMap.get(name);
        if (values == null || values.length == 0) {
            return null;
        } else {
            return values[0];
        }
    }

    @Override
    public String[] getParameterValues(String name) {
        return springParamMap.get(name);
    }

    @Override
    public Map getParameterMap() {
        return springParamMap;
    }

    @Override
    public Enumeration getParameterNames() {
        return Collections.enumeration(springParamMap.keySet());
    }

    static protected Map convertJsonParameters(Map jsonParamMap) {
        HashMap resultMap = new HashMap();
        HashMap workMap = new HashMap();

        for (Map.Entry entry : jsonParamMap.entrySet()) {
            String name = entry.getKey();
            String[] values = entry.getValue();

            if (JsonBinderUtils.isJson(values)) {
                JsonBinderUtils.convertJsonString(workMap, name, values);

                for (Map.Entry workEntry : workMap.entrySet()) {
                    resultMap.put(workEntry.getKey(), new String[] { workEntry.getValue() });
                }

                workMap.clear();
            } else {
                resultMap.put(name, values);
            }
        }

        return resultMap;
    }

    static public boolean jsonInside(HttpServletRequest request) {
        if (request instanceof JsonAwareHttpServletRequest) {
            return false;
        }

        Collection valuesForAllKeys = request.getParameterMap().values();

        for (String[] valuesPerKey : valuesForAllKeys) {
            if (JsonBinderUtils.isJson(valuesPerKey)) {
                return true;
            }
        }

        return false;
    }
}
JsonBinderUtils
public class JsonBinderUtils {

    static public void convertJsonString(Map resultMap, String name, String[] values) {
        if (values.length == 1) {
            convertJsonString(resultMap, name, values[0]);
        } else {
            for (int i = 0; i < values.length; i++) {
                convertJsonString(resultMap, name + "[" + i + "]", values[i]);
            }
        }
    }

    static public void convertJsonString(Map resultMap, String name, String value) {
        if (!isJson(value)) {
            resultMap.put(name, value);
            return;
        }

        ArrayList resultList = new ArrayList();
        LinkedList jsonList = new LinkedList();

        if (isJsonObject(value)) {
            jsonList.add(new PropertyValue(name, JSONObject.fromObject(value)));
        } else if (isJsonArray(value)) {
            jsonList.add(new PropertyValue(name, JSONArray.fromObject(value)));
        }

        convertJsonObject(resultList, jsonList);

        for (PropertyValue pv : resultList) {
            resultMap.put(pv.getName(), (String) pv.getValue());
        }
    }

    @SuppressWarnings("unchecked")
    static protected void convertJsonObject(List resultList,
            LinkedList jsonList) {
        while (jsonList.size() > 0) {
            PropertyValue pv = jsonList.poll();
            String name = pv.getName();

            if (pv.getValue() instanceof JSONObject) {
                JSONObject jsonObject = (JSONObject) pv.getValue();

                if (jsonObject.isNullObject()) {
                    continue;
                }

                Set> entrySet = jsonObject.entrySet();

                for (Map.Entry entry : entrySet) {
                    String pvName = name + "." + entry.getKey();
                    Object pvValue = entry.getValue();

                    if (pvValue instanceof JSONObject || pvValue instanceof JSONArray) {
                        jsonList.addLast(new PropertyValue(pvName, pvValue));
                    } else {
                        resultList.add(new PropertyValue(pvName, pvValue.toString()));
                    }
                }
            } else if (pv.getValue() instanceof JSONArray) {
                JSONArray jsonArray = (JSONArray) pv.getValue();

                for (int i = 0; i < jsonArray.size(); i++) {
                    String pvName = name + "[" + i + "]";
                    Object pvValue = jsonArray.get(i);

                    if (pvValue instanceof JSONObject || pvValue instanceof JSONArray) {
                        jsonList.addLast(new PropertyValue(pvName, pvValue));
                    } else {
                        resultList.add(new PropertyValue(pvName, pvValue.toString()));
                    }
                }
            }
        }
    }

    static public boolean isJsonObject(String value) {
        return value != null && (value.startsWith("{") && value.endsWith("}"));
    }

    static public boolean isJsonArray(String value) {
        return value != null && (value.startsWith("[") && value.endsWith("]"));
    }


    static public boolean isJson(String value) {
        return isJsonObject(value) || isJsonArray(value);
    }

    static public boolean isJson(String[] valueArray) {
        if (valueArray.length == 0) {
            return false;
        }

        int jsonObjectCount = 0;
        int jsonArrayCount = 0;

        for (String value : valueArray) {
            if (isJsonObject(value)) {
                jsonObjectCount++;
            } else if (isJsonArray(value)) {
                jsonArrayCount++;
            }
        }

        return (jsonObjectCount == valueArray.length || jsonArrayCount == valueArray.length);
    }

}

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • It's an interesting approach. I worked on an alternate approach that leverages JAX-RS, JAXB and Jetty. I added an integration that will allow you to either create "Controllers" using JAX-RS, which implicitly do incoming and outgoing JSon conversion (among other things). You can also add an annotation to existing Spring 2.5 annotated controllers, plus some configuration, that will allow you to do something like:

    @RequestMapping(...POST)
    public void processData(@RestfulData("application/json") MyObject myObject){
    // do something with myObject
    }

    Keep an eye out for it in the next release of JBoss Resteasy (beta9).

    The Spring guys are also working on RESTifying the MVC framework in Spring 3.0, which should include similar functionality.

    Posted by: sduskis on November 21, 2008 at 11:17 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds