Nashorn ScriptObjectMirror JS -> Java 类型转换

Nashorn ScriptObjectMirror JS -> Java type conversion

当我使用 Nashorn ScriptObjectMirror.get() 访问 JavaScript 对象的成员变量时,returned 对象的类型似乎在运行时确定。例如,如果值适合 Java int,get() 似乎 return 适合 Java 整数。如果该值不适合 int,get() 似乎 return 一个 Java Long,依此类推。

现在,我使用 instanceof 检查类型并将值转换为 long。

在Java中有没有更方便的方法来获取成员的值而不丢失并且不检查类型?也许 Nashorn 总能给我一个 Java Double,如果成员不是数字则抛出错误。

我可以想象这是一个相当狭窄的案例,可能不应该由 Nashorn 处理...

示例:

package com.tangotangolima.test.nashorn_types;

import jdk.nashorn.api.scripting.ScriptObjectMirror;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.StringReader;

public class Main {

    public static void main(String[] args) throws ScriptException {
        final ScriptEngineManager mgr = new ScriptEngineManager();
        final ScriptEngine js = mgr.getEngineByName("nashorn");

        final String script = "" +
                "var p = 1;" +
                "var q = " + (Integer.MAX_VALUE + 1L) + ";" +
                "var r = {" +
                "s: 1," +
                "t: " + (Integer.MAX_VALUE + 1L) +
                " };";

        js.eval(new StringReader(script));

        say(js.get("p").getClass().getName());      // -> java.lang.Integer
        say(js.get("q").getClass().getName());      // -> java.lang.Long

        final ScriptObjectMirror r = (ScriptObjectMirror) js.get("r");

        say(r.get("s").getClass().getName());       // -> java.lang.Integer
        say(r.get("t").getClass().getName());       // -> java.lang.Long
    }

    static void say(String s) {
        System.out.println(s);
    }
}

推荐的方法是检查 java 代码中的 "instanceof java.lang.Number" -- 如果您期望 JavaScript "number" 值。一旦转换为 Number,您可以通过调用 intValue、longValue 等方法转换为 int、long、double

这段代码可以做ScriptObjectMirror JS -> Java转换

private static Object convertIntoJavaObject(Object scriptObj) {
    if (scriptObj instanceof ScriptObjectMirror) {
        ScriptObjectMirror scriptObjectMirror = (ScriptObjectMirror) scriptObj;
        if (scriptObjectMirror.isArray()) {
            List<Object> list = Lists.newArrayList();
            for (Map.Entry<String, Object> entry : scriptObjectMirror.entrySet()) {
                list.add(convertIntoJavaObject(entry.getValue()));
            }
            return list;
        } else {
            Map<String, Object> map = Maps.newHashMap();
            for (Map.Entry<String, Object> entry : scriptObjectMirror.entrySet()) {
                map.put(entry.getKey(), convertIntoJavaObject(entry.getValue()));
            }
            return map;
        }
    } else {
        return scriptObj;
    }
}

public static void main(String[] args) throws ScriptException, NoSuchMethodException {
    final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
    engine.eval("function objProvider(){return {a:1, b:'2','c': true,'d': {'e':[],'f':['1',{'g':45}]}};}");
    final Object scriptObj = ((Invocable) engine).invokeFunction("objProvider");

    Object javaObj = convertIntoJavaObject(scriptObj);
    System.out.println(javaObj);
    //{a=1, b=2, c=true, d={e=[], f=[1, {g=45}]}}
}

我真的很喜欢 Igor's 方法。这是他的 convertToJavaObject() 代码,作为仅使用标准 Java 的完整程序,除了包含一个 toJava() 之外,还包含一个相反的 toJavascript()

import jdk.nashorn.api.scripting.ScriptObjectMirror;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class Main {

  @SuppressWarnings("removal")
  private static Object toJava(Object jsObj) {
    if (jsObj instanceof ScriptObjectMirror) {
      var jsObjectMirror = (ScriptObjectMirror) jsObj;
      if (jsObjectMirror.isArray()) {
        var list = new ArrayList<>();
        for (Map.Entry<String, Object> entry : jsObjectMirror.entrySet()) {
          list.add(toJava(entry.getValue()));
        }
        return list;
      } else {
        var map = new HashMap<String, Object>();
        for (Map.Entry<String, Object> entry : jsObjectMirror.entrySet()) {
          map.put(entry.getKey(), toJava(entry.getValue()));
        }
        return map;
      }
    } else {
      return jsObj;
    }
  }

  public static void main(String[] args) throws ScriptException, NoSuchMethodException {
    var code = String.join("\n",
"function objProvider() {",
"  return {a:1, b:'2','c': true,'d': {'e':[],'f':['1',{'g':45}]}}",
"}",
"function toJavascript (jObj) {",
"  if (jObj instanceof java.util.List) {",
"    var l = []; for each (var item in jObj) {",
"      l.push(toJavascript(item));",
"    }",
"    return l;",
"  }",
"  if (jObj instanceof java.util.Map) {",
"    var m = {}; for each (var key in jObj.keySet()) {",
"      m[key] = toJavascript(jObj.get(key));",
"    }",
"    return m;",
"  }",
"  return jObj;",
"}"
);
    var engine = new ScriptEngineManager().getEngineByName("nashorn");
    engine.eval(code);
    var jsObj = ((Invocable) engine).invokeFunction("objProvider");
    var formatted = ((Invocable) engine).invokeMethod(engine.eval("JSON"), "stringify", jsObj);
    System.out.println("JSON.stringify(jsObj): " + formatted);
    var javaObj = toJava(jsObj);
    System.out.println("javaObj: " + javaObj);
    //{a=1, b=2, c=true, d={e=[], f=[1, {g=45}]}}
    var newJsObj = ((Invocable) engine).invokeFunction("toJavascript", javaObj);
    formatted = ((Invocable) engine).invokeMethod(engine.eval("JSON"), "stringify", newJsObj);
    System.out.println("JSON.stringify(newJsObj): " + formatted);
    // just to show this doesn't work without conversion
    formatted = ((Invocable) engine).invokeMethod(engine.eval("JSON"), "stringify", javaObj);
    System.out.println("JSON.stringify(javaObj):  " + formatted);
  }
}