Wednesday, September 13, 2006

Spring MVC and AJAX with JSON

One of the main decisions to be taken while developing AJAX applications is the format of messages passed by the server to the client browser. There are many options to choose from including plain text, XML, CSV etc. One of the more popular choices today is the JavaScript Object Notation (JSON). JSON provides a nice name-value pair data format that is easy to generate and parse. This is especially true when using an AJAX toolkit like Dojo that provides built-in functionality to parse JSON messages at the client. If you are using Spring MVC as your web framework, generation of these JSON messages is very straight-forward as well. Below we understand how to produce JSON messages while using Spring MVC.

Spring MVC defines the View interface to render views to the client. The framework provides a number of implementations including JstlView, RedirectView, TilesView etc. In order to return JSON messages we implement the View interface to create a new class that returns data formatted using the JSON notation. We shall call this class JSONView.

The method that we need to implement is render(Map model, HttpServletRequest request, HttpServletResponse response). The render method accepts a Map as it's first parameter and produces the output. We could manually iterate through the Map, process and produce the JSON output. However, there is a Java library called JSON-lib that produces the JSON notation. The code below shows our JSONView class that can be used as a Spring MVC View to return JSON output to the client.

import java.io.PrintWriter;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONObject;
import org.springframework.web.servlet.View;
 
public class JSONView implements View {
    public void render(Map map, HttpServletRequest request,
    HttpServletResponse response) throws Exception {
        JSONObject jsonObject = JSONObject.fromMap(map);
        PrintWriter writer = response.getWriter();
        writer.write(jsonObject.toString());
    }
 
    ...
}

As can be seen from the code above, Spring exploits MVC to it's full potential and provides the flexibility to tailor the view to exactly suit our needs.

9 comments:

Andres Almiray said...

Hi Vijay, what you describe in your blog is the main reason why Json-lib was born, I was working with an AJAX solution that did not have support for Spring MVC. BTW, the code was submitted to the Spring JIRA with issue SPR-2210 :-)

Unknown said...

Vijay,
First off, thanks for showing how to create a custom JSON view. But I am having trouble assigning this view to a viewResolver. Which resolver should be used and does it required any properties?

Many thanks,
Peter

Vijay Albuquerque said...

Hi Peter,
JSONView works a little differently to the 'traditional' ViewResolver model of SpringMVC. The JSONView view is returned back to the client using the usual ModelAndView object. However, instead of passing a view name to the ModelAndView, we would create a new JSONView object and pass a map of our values. For example, the code below goes into the controller.

Map<String, String> map = new TreeMap<String, String>();
map.put("divId", "divMain");
map.put("content", "This is an example.");

modelAndView = new ModelAndView(new JSONView(), map);

The above code will then create a JSONView view with the values from our Map and the resuting JSON String will be returned to the client. Since we have not passed a view name to the ModelAndView object, the ViewResolver is 'bypassed'.

vivi said...

i am trying to use json with spring for dynamic object creation,according to the selected value in a combo i want to create a specific object that it's attributrs will be displayed by jsp.i created a new controller
and configured the url="jsonTest.ajax" to it (by project-servlet.xml) and was asked to supply a view(setFormView).
The controller fromBackingObject is called but from should i return the ModelAndView? should i call some controller function???

Vijay Albuquerque said...

Hi vivi,
From your question I guess you are uncertain about how to return back the ModelAndView object from your Controller.

You would need to create a new ModelAndView object passing in the JSONView and a Map of values as parameters.

This has been discussed in a previous comment (4/11/2007 11:27AM ).

Hope this helps...

Abhi said...

Nice approach Vijay!
I was just wondering whether custom objects would work just as a Map obj

Wouter Schaubroeck said...

for Spring 2.2.5 this would become:

public class JsonView implements View {

/**
* Return the content type of the view, if predetermined.
* Can be used to check the content type upfront, before the actual rendering process.
* @return String - null
*/
public String getContentType() {
return null;
}

public void render(Map map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
JSONObject jsonObject = (JSONObject) JSONSerializer.toJSON(map);
PrintWriter writer = httpServletResponse.getWriter();
writer.write(jsonObject.toString());
}
}

Daniel Alexiuc said...

If you are just using org.json you can do this:

public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
JSONObject jsonObject = new JSONObject(model);
PrintWriter writer = response.getWriter();
writer.write(jsonObject.toString());
}

Vijay Kallepalli said...

Vijay, the approach is excellent. We implmented the same in our project (i.e., using Spring MVC). However, there is one issue the value changed in the field is not being binded to the Model that is form bean in the MVC. It retains the old value. We have to explicitly pass value of the field changed in the query string as request parameter. The javascript method that is performing the AJAX request is as follows:
function getXhrRequest(searchTerms) {
dojo.xhrPost({
url: dojo.byId("initialContactForm").action +"?perform=zip",
timeout: 6000,
handleAs: "text",
preventCache: true,
load: function(res) {
setFieldClass();
},
error: commonError
});
}

In our controller the control is going to the following method:
public ModelAndView processZip(HttpServletRequest request, HttpServletResponse response, Object command,
BindException errors) throws Exception {
InitialContactForm form = (InitialContactForm) command;
Map model = new HashedMap();
return new ModelAndView(new JSONView(),model);
}

When I look at the value of the field in the form in debug mode, it is still the old value. For some reason it is not being binded. The supressBinding method is returning false. The only way I could see the new value entered by the user is by passing the value as a parameter and using request.getParameter("parameterName"). Are we missing something in the process?