Java output JSON to CSV file

angularjs, csv, java, json, servlets

I'm not too familiar with how to output files back to the client with Java. I am trying to create a CSV file to be sent back to the client and opened in Excel.

I found this tool for the server side creation. I'm not sure exactly how to use it to return the actual file though. Here is a sample of code I have used to return a txt file that I think I can use parts of the response for, but I'm not fetching a file anymore since I'm creating this CSV so I'm not sure what I can use.

In the code below my biggest question is what do I have to return with the controller and how do I accomplish that? I'm not sure what I need to be returning between that and also from the CSV writer to the controller. Any help would be appreciated.

Here's my code so far:

Controller:

@RequestMapping(value = "/web/csvexport", method = RequestMethod.POST)protected void processCSV(HttpServletRequest request, HttpServletResponse response, @RequestBody String jsonRequest)        throws ServletException, IOException {    response.setContentType("text/html;charset=UTF-8");    try {        CSVWriter csvWriter = new CSVWriter();         JsonFlattener jsonFlattener = new JsonFlattener();         String fileName = "StandardQuery";         csvWriter.writeAsCSV(jsonFlattener.parseJson(jsonRequest), fileName);     } catch (Exception e) {        System.out.println("Exception: " + e);    }}

CVS Writer:

public class CSVWriter {public void writeAsCSV(List<Map<String, String>> flatJson, String fileName) throws FileNotFoundException {    Set<String> headers = collectHeaders(flatJson);    String output = StringUtils.join(headers.toArray(), ",") + "\n";    for (Map<String, String> map : flatJson) {        output = output + getCommaSeperatedRow(headers, map) + "\n";    }    writeToFile(output, fileName);}private void writeToFile(String output, String fileName) throws FileNotFoundException {    BufferedWriter writer = null;    try {        writer = new BufferedWriter(new FileWriter(fileName));        writer.write(output);    } catch (IOException e) {        e.printStackTrace();    } finally {        close(writer);    }}private void close(BufferedWriter writer) {    try {        if (writer != null) {            writer.close();        }    } catch (IOException e) {        e.printStackTrace();    }}private String getCommaSeperatedRow(Set<String> headers, Map<String, String> map) {    List<String> items = new ArrayList<String>();    for (String header : headers) {        String value = map.get(header) == null ? "" : map.get(header).replace(",", "");        items.add(value);    }    return StringUtils.join(items.toArray(), ",");}private Set<String> collectHeaders(List<Map<String, String>> flatJson) {    Set<String> headers = new TreeSet<String>();    for (Map<String, String> map : flatJson) {        headers.addAll(map.keySet());    }    return headers;}

}

Json Flattener:

public class JsonFlattener {public Map<String, String> parse(JSONObject jsonObject) {    Map<String, String> flatJson = new HashMap<String, String>();    flatten(jsonObject, flatJson, "");    return flatJson;}public List<Map<String, String>> parse(JSONArray jsonArray) {    List<Map<String, String>> flatJson = new ArrayList<Map<String, String>>();    int length = jsonArray.length();    for (int i = 0; i < length; i++) {        JSONObject jsonObject = jsonArray.getJSONObject(i);        Map<String, String> stringMap = parse(jsonObject);        flatJson.add(stringMap);    }    return flatJson;}public List<Map<String, String>> parseJson(String json) throws Exception {    List<Map<String, String>> flatJson = null;    try {        JSONObject jsonObject = new JSONObject(json);        flatJson = new ArrayList<Map<String, String>>();        flatJson.add(parse(jsonObject));    } catch (JSONException je) {        flatJson = handleAsArray(json);    }    return flatJson;}private List<Map<String, String>> handleAsArray(String json) throws Exception {    List<Map<String, String>> flatJson = null;    try {        JSONArray jsonArray = new JSONArray(json);        flatJson = parse(jsonArray);    } catch (Exception e) {        throw new Exception("Json might be malformed");    }    return flatJson;}private void flatten(JSONArray obj, Map<String, String> flatJson, String prefix) {    int length = obj.length();    for (int i = 0; i < length; i++) {        if (obj.get(i).getClass() == JSONArray.class) {            JSONArray jsonArray = (JSONArray) obj.get(i);            if (jsonArray.length() < 1) continue;            flatten(jsonArray, flatJson, prefix + i);        } else if (obj.get(i).getClass() == JSONObject.class) {            JSONObject jsonObject = (JSONObject) obj.get(i);            flatten(jsonObject, flatJson, prefix + (i + 1));        } else {            String value = obj.getString(i);            if (value != null)                flatJson.put(prefix + (i + 1), value);        }    }}private void flatten(JSONObject obj, Map<String, String> flatJson, String prefix) {    Iterator iterator = obj.keys();    while (iterator.hasNext()) {        String key = iterator.next().toString();        if (obj.get(key).getClass() == JSONObject.class) {            JSONObject jsonObject = (JSONObject) obj.get(key);            flatten(jsonObject, flatJson, prefix);        } else if (obj.get(key).getClass() == JSONArray.class) {            JSONArray jsonArray = (JSONArray) obj.get(key);            if (jsonArray.length() < 1) continue;            flatten(jsonArray, flatJson, key);        } else {            String value = obj.getString(key);            if (value != null && !value.equals("null"))                flatJson.put(prefix + key, value);        }    }}

}

Here's the service that I'm calling the controller from. I used this to return a .txt file before so I'm not sure how usable it is, but I think if I stream the file back it will handle it…:

getFile: function(jsonObj, fileName) {         var _defer = $q.defer();         $http.post("/web/csvexport/", jsonObj).success(function(data, status, headers) {            var octetStreamMime = "application/octet-stream";            // Get the headers            headers = headers();            // Get the filename from the x-filename header or default to "download.bin"            //var filename = headers["x-filename"] || "logfile.log";            var filename = fileName;             // Determine the content type from the header or default to "application/octet-stream"            var contentType = headers["content-type"] || octetStreamMime;            if(navigator.msSaveBlob)            {                // Save blob is supported, so get the blob as it's contentType and call save.                var blob = new Blob([data], { type: contentType });                navigator.msSaveBlob(blob, filename);                console.log("SaveBlob Success");            }            else            {                // Get the blob url creator                var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;                if(urlCreator)                {                    // Try to use a download link                    var link = document.createElement("a");                    if("download" in link)                    {                        // Prepare a blob URL                        var blob = new Blob([data], { type: contentType });                        var url = urlCreator.createObjectURL(blob);                        link.setAttribute("href", url);                        // Set the download attribute (Supported in Chrome 14+ / Firefox 20+)                        link.setAttribute("download", filename);                        // Simulate clicking the download link                        var event = document.createEvent('MouseEvents');                        event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);                        link.dispatchEvent(event);                        console.log("Download link Success");                    } else {                        // Prepare a blob URL                        // Use application/octet-stream when using window.location to force download                        var blob = new Blob([data], { type: octetStreamMime });                        var url = urlCreator.createObjectURL(blob);                        window.location = url;                        console.log("window.location Success");                    }                } else {                    console.log("Not supported");                }            }

Best Solution

Firstly, why don't use CSV mime type instead of html ?

replace

 response.setContentType("text/html;charset=UTF-8");

by

response.setContentType("text/csv");

And do you know that Jackson, Java JSON API handle CSV ? see https://github.com/FasterXML/jackson-dataformat-csv

Finaly, in the controler you need to use the printWriter from the response to write the CSV.

Dont forget, to prefer Stream or BufferedString to handle large file and have better performances.