Servlet Filter to Convert XML to JSON

In a recent project I was facing the problem that a JSON interface had to be added to an already existing XML interface. The XML was rendered using JSPs. That’s fine and I didn’t want to change that. I also didn’t want to duplicate the JSPs by writing additional JSPs that generate JSON. The easiest solution that came into my mind was to add a simple servlet filter that converts the generated XML to JSON on the fly when JSON format is requested.

In our web application the desired format (XML or JSON) was added as a request parameter to the HTTP request). All the servlet filter has to do is to check this parameter to decide whether the XML should be converted to JSON.

In order to compile and use the code you also need the following libraries:

  1. servlet-api 2.5
  2. dom4j 1.6.1 (http://dom4j.sourceforge.net/)
  3. jsonwriter (https://github.com/kkrugler/jsonwriter) A nice extension to dom4j written by Ken Krugler and Chris Schneider aka Schmed that allows to write a dom4j Document as json. Thanks to Ken and Schmed to make this library open source. I actually saved my blog.

Here is the code:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

/**
 * Converts xml to json if query parameter format=json is available.
 */
public class JsonFilter implements Filter {

    public void destroy() {
        // do nothing
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        if (res instanceof HttpServletResponse) {
            HttpServletResponse response = (HttpServletResponse) res;
            if ("json".equals(req.getParameter("format"))) {
                JsonResponseWrapper wrappedResponse = new JsonResponseWrapper(response);
                chain.doFilter(req, wrappedResponse);
                wrappedResponse.finishResponse();
            } else {
                chain.doFilter(req, res);
            }
        }
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        // do nothing
    }
}

The JsonResponseWrapper does the XML to JSON conversion. Please note that this implementation buffers the complete XML in memory. This approach is not appropriate for very large XML, but it was working very well for our application:

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.io.JSONFormat;
import org.dom4j.io.JSONWriter;

/**
 * Converts xml to json.
 */
public class JsonResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayServletOutputStream _servletOutputStream;
    private PrintWriter _printWriter;

    public JsonResponseWrapper(HttpServletResponse response) {
        super(response);
        response.setContentType("text/x-json");
    }

    @Override
    public void setContentLength(int len) {
        // ignore
    }

    @Override
    public void setContentType(String type) {
        // ignore
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (_printWriter != null) {
            throw new IllegalStateException("Servlet already accessed print writer.");
        }

        if (_servletOutputStream == null) {
            _servletOutputStream = new ByteArrayServletOutputStream();
        }
        return _servletOutputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (_printWriter == null && _servletOutputStream != null) {
            throw new IllegalStateException("Servlet already accessed output stream.");
        }

        if (_printWriter == null) {
            _servletOutputStream = new ByteArrayServletOutputStream();
            Writer writer = new OutputStreamWriter(_servletOutputStream, getResponse()
                    .getCharacterEncoding());
            _printWriter = new PrintWriter(writer);
        }
        return _printWriter;
    }

    public void finishResponse() throws IOException {
        if (_servletOutputStream != null) {
            if (_printWriter != null) {
                _printWriter.flush();
            }
            try {
                Document document = DocumentHelper.parseText(new String(
                        _servletOutputStream.getBytes(), getResponse()
                                .getCharacterEncoding()));
                JSONWriter writer = new JSONWriter(getResponse().getWriter(),
                        JSONFormat.RABBIT_FISH);
                writer.write(document);
                writer.flush();
                getResponse().getWriter().write("\n");
            } catch (DocumentException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public void resetBuffer() {
        _servletOutputStream = null;
        _printWriter = null;
        super.resetBuffer();
    }

    @Override
    public void reset() {
        _servletOutputStream = null;
        _printWriter = null;
        super.reset();
    }
}

The ByteArrayServletOutputStream just caches the XML in memory.

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.servlet.ServletOutputStream;

public class ByteArrayServletOutputStream extends ServletOutputStream {

    private ByteArrayOutputStream _outputStream;

    public ByteArrayServletOutputStream() {
        _outputStream = new ByteArrayOutputStream();
    }

    @Override
    public void write(int b) throws IOException {
        _outputStream.write(b);
    }

    @Override
    public void write(byte[] b) throws IOException {
        _outputStream.write(b);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        _outputStream.write(b, off, len);
    }

    @Override
    public void close() throws IOException {
        _outputStream.close();
    }

    public byte[] getBytes() {
        return _outputStream.toByteArray();
    }
}

The code was pretty straight forward to write. But maybe it is still helpful for someone who has a similar problem. It can be optimized to use a streaming approach versus the in memory buffering of the XML. But that wasn’t needed for my purposes, so I leave this up to you. ;-)

Advertisements

5 comments so far

  1. Fernanda on

    Thank you for the example, really appreciated

  2. Yadu on

    Nice example.

  3. Tom Anderson on

    What version of org.dom4j are you using? I’ve tried using your examples but keep getting hung on the import org.dom4j.io.JSONFormat and import org.dom4j.io.JSONWriter – both in the import and the instantiation of such.

    The JavaDoc for 1.4 and 1.5.2 and 1.6.1 do not indicate any such classes. Are you using another jar/package besides that provided at sourceforge for the org.dom4j project?

    Thanks.

    • Peter Voss on

      You are right. We must have been using a hacked version of dom4j in that project. :-( I will have a look at this an update the example in the next couple of days.

      Thanks for pointing this out,
      –Peter

  4. Rahul on

    Can you please update on the above comment of different version of dom4j

    Rahul


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: