7
\$\begingroup\$

I have written a Java JSON stringifier with the Nashorn API. Since this code is slow, I wanted to ask if I can improve it. My target is the highest possible performance to make it an alternative to other JSON libraries.

JSON.java

package com.example.json;

import java.util.Map;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import com.example.json.util.TextFile;

public class JSON {

    private static final Invocable inv = create();

    private static final Invocable create() {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        try {
            engine.eval(TextFile.readFromResource("/js/json-stringify.js"));
        } catch (ScriptException e) {
            e.printStackTrace();
        }
        return (Invocable) engine;
    }

    public static final String stringify(Map<String, Object> json) {
        try {
            return (String) inv.invokeFunction("stringifyJSON", json);
        } catch (ScriptException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

}

JSONObject.java

package com.example.json.stringify;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class JSONObject {

    private Map<String, Object> map = new HashMap<String, Object>();

    public void appendChild(String key, Object object) {
        map.put(key, object);
    }

    public static final Map<String, Object> convertToJavaScript(JSONObject obj) {
        return obj.map;
    }

}

JSONArray.java

package com.example.json.stringify;

public class JSONArray {

    private Object[] array;

    public JSONArray(int length) {
        array = new Object[length];
    }

    public void set(int index, Object object) {
        if (index >= 0 && index <= array.length - 1) array[index] = object;
    }

    public static final Object[] convertToJavaScript(JSONArray arr) {
        return arr.array;
    }

}

/js/json-stringify.js

function stringifyJSON(json) {

    var JSONObject = Java.type("com.example.json.stringify.JSONObject");
    var JSONArray = Java.type("com.example.json.stringify.JSONArray");

    return JSON.stringify(convertJSONObject(json));

    function convert(json) {
        if (isJSONObject(json)) return convertJSONObject(JSONObject.convertToJavaScript(json));
        if (isJSONArray(json)) return convertJSONArray(JSONArray.convertToJavaScript(json));
        return json;
    }

    function convertJSONObject(object) {
        var jsobject = {};
        for (key in object) {
            jsobject[key] = convert(object.get(key));
        }
        return jsobject;
    }

    function convertJSONArray(array) {
        var jsarray = [];
        for (var index = 0, len = array.length; index < len; index++) {
            jsarray.push(convert(array[index]));
        }
        return jsarray;
    }

    function isJSONObject(json) Object.prototype.toString.call(json) === "[object com.example.json.stringify.JSONObject]";
    function isJSONArray(json) Object.prototype.toString.call(json) === "[object com.example.json.stringify.JSONArray]";

}

TextFile.java (from this post)

Example

I used the same example JSON from my JSON parser.

Map<String, Object> json = new HashMap<String, Object>();
JSONArray employees = new JSONArray(3);

JSONObject employee1 = new JSONObject();
employee1.appendChild("firstName", "John");
employee1.appendChild("lastName", "Doe");
employees.set(0, employee1);

JSONObject employee2 = new JSONObject();
employee2.appendChild("firstName", "Anna");
employee2.appendChild("lastName", "Smith");
employees.set(1, employee2);

JSONObject employee3 = new JSONObject();
employee3.appendChild("firstName", "Peter");
employee3.appendChild("lastName", "Jones");
employees.set(2, employee3);

json.put("employees", employees);

System.out.println(JSON.stringify(json));

Result

{"employees":[{"firstName":"John","lastName":"Doe"},{"firstName":"Anna","lastName":"Smith"},{"firstName":"Peter","lastName":"Jones"}]}

\$\endgroup\$

1 Answer 1

8
\$\begingroup\$

you can get JSON from engine by

ScriptObjectMirror json = (ScriptObjectMirror) engine.eval("JSON");

and call it's parse or stringify method via:

json.callMember("stringify", object);
json.callMember("parse", str)
\$\endgroup\$
4
  • 1
    \$\begingroup\$ While I know this works for the parse case, I’m wondering if you’ve tried it with the stringify case. I’ve tried it myself and found that I just get either undefined or null from it in the end. And I think that’s because Nashorn’s JSON.stringifyby design can’t stringify Java objects. See mail.openjdk.java.net/pipermail/nashorn-dev/2013-September/… and see the code at hg.openjdk.java.net/jdk8/jdk8/nashorn/file/096dc407d310/src/jdk/… \$\endgroup\$ Commented Mar 4, 2016 at 6:45
  • \$\begingroup\$ It worked for me, when I get the object from the JS context when I use the following code: JSObject jsObject = (JSObject) engine.getContext().getAttribute(PROPERTY); ScriptObjectMirror JSON = (ScriptObjectMirror) engine.eval("JSON"); Object stringified = JSON.callMember("stringify", jsObject); but not when I get the JSObject by calling a JS function from Java :/ \$\endgroup\$ Commented May 25, 2017 at 11:01
  • \$\begingroup\$ Digging into the NativeJSON.java implementation, it's clear that their implementation expects a JSObject. When you get 'undefined' from the call to stringify from java, it probably means that the ScriptObjectMirror was passed in and not the ScriptObject. That is what the comment above is digging out explicitly. There were bugs fixed in about JDK8u51around this sort of unwrapping stuff (if I recall correctly). \$\endgroup\$ Commented Nov 10, 2017 at 10:56
  • \$\begingroup\$ It turns out that if you pass a ScriptObjectMirror that was created in one engine to the JSON.stringify method in another engine, the object is not unwrapped as expected and therefore returns 'undefined'. \$\endgroup\$ Commented Nov 10, 2017 at 11:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.