Using Rhino and JSR-223 to read a JSON file

codefinger picture codefinger · Apr 12, 2012 · Viewed 9.1k times · Source

I'm trying to read a JSON file with some JavaScript that I'm eval-ing with Rhino through the Java JSR-223 API. What I'm trying to do works with the Rhino shell, but not with the embedded Javascript in my Java code.

Here's what works:

$ java -jar js.jar
js> readFile('foo.json');
{y:"abc"}

Here is what does not work:

ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("js");
engine.eval("readFile('foo.json')"); 

I get this error:

Exception in thread "main" javax.script.ScriptException: 
sun.org.mozilla.javascript.internal.EcmaError: ReferenceError: "readFile" is not defined. (<Unknown source>#4) in <Unknown source> at line number 4
    at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:153)
    at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:232)

How can i get the readFile to be available in my engine?

Answer

NullPointerException picture NullPointerException · Apr 13, 2012

If you are just trying to parse JSON, I would suggest using a JSON parsing library like Jackson or Jettison, it's a much easier approach. The readFile call returns a string, and it is not actually converting the contents into a JavaScript object. A JSON.parse or JavaScript eval call on the string would be needed in order to accomplish that.

If you really need to use the JavaScript engine to parse JSON and you were using Java 7 you could do something like this (but I wouldn't advise it)...

public static void main(final String args[]) throws Exception {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("js");
    Object val = null;
    Scanner s = null;
    try {
        s = new Scanner(new File("foo.json"));
        s.useDelimiter("\\Z");
        engine.getBindings(ScriptContext.GLOBAL_SCOPE).put("json", s.next());
    } finally {
        if (s != null) {
            s.close();
        }
    }
    // The default JavaScript engine in Java 6 does not have JSON.parse
    // but eval('(' + json + ')') would work
    val = engine.eval("JSON.parse(json)");

    // The default value is probably a JavaScript internal object and not very useful
    System.out.println(val.getClass() + " = " + val);
    // Java 7 uses Rhino 1.7R3 the objects will implement Map or List where appropriate
    // So in Java 7 we can turn this into something a little more useable
    // This is where Java 6 breaks down, in Java 6 you would have to use the 
    // sun.org.mozilla.javascript.internal classes to get any useful data
    System.out.println(convert(val));
}

@SuppressWarnings("unchecked")
public static Object convert(final Object val) {
    if (val instanceof Map) {
        final Map<String, Object> result = new HashMap<String, Object>();
        for (final Map.Entry<String, Object> entry: ((Map<String, Object>) val).entrySet()) {
            result.put(entry.getKey(), convert(entry.getValue()));
        }
        return result;
    } else if (val instanceof List) {
        final List<Object> result = new ArrayList<Object>();
        for (final Object item: ((List<Object>) val)) {
            result.add(convert(item));
        }
        return result;
    }
    if (val != null) {
        System.out.println(val.getClass() + " = " + val);
    }
    return val;
}

The JavaScript engine provided in Java has excluded some of the features provided in Rhino. The readFile function is actually provided by the Rhino Shell, not the engine implementation. Oracle also provides functions accessible by the jrunscript shell which are not in the engine.

Here's an example that mimics the Rhino shell readFile function and adds it to the JavaScript scope based upon the answer to this question: How can I add methods from a Java class as global functions in Javascript using Rhino?

public static void main(final String args[]) throws Exception {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("js");
    engine.getBindings(ScriptContext.GLOBAL_SCOPE).put("utils", new Utils());
    engine.eval("for(var fn in utils) {\r\n" +
            "  if(typeof utils[fn] === 'function') {\r\n" +
            "    this[fn] = (function() {\r\n" +
            "       var method = utils[fn];\r\n" +
            "       return function() {\r\n" +
            "         return method.apply(utils,arguments);\r\n" +
            "       };\r\n" +
            "    })();\r\n" +
            "  }\r\n" +
            "}");
    engine.eval("println(readFile('foo.json'))");
}

static class Utils {
    public String readFile(final String fileName) throws FileNotFoundException {
        return readFile(fileName, null);
    }
    public String readFile(final String fileName, final String encoding) throws FileNotFoundException {
        Scanner s = null;
        try {
            s = new Scanner(new File(fileName), (encoding == null)? Charset.defaultCharset().name(): encoding);
            s.useDelimiter("\\Z");
            return s.next();
        } finally {
            if (s != null) {
                s.close();
            }
        }
    }
}