Nashorn ScriptObjectMirror JS -> Преобразование типов Java

Когда я обращаюсь к переменной-члену объекта JavaScript с помощью Nashorn ScriptObjectMirror.get(), тип возвращаемого объекта, похоже, определяется во время выполнения. Например, если значение соответствует типу int Java, функция get() возвращает целое число Java. Если значение не помещается в int, get(), кажется, возвращает Java Long и так далее.

Прямо сейчас я использую instanceof для проверки типа и преобразования значения в длинное.

Есть ли более удобный способ получить значение члена без потерь и без проверки типа в Java? Возможно, Нэсхорн всегда мог дать мне Java Double, выдав ошибку, если член не является числовым.

Я могу представить, что это довольно узкое дело, которым Нэшорн, вероятно, не должен заниматься...

Пример:

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);
    }
}

person Ishan    schedule 10.11.2015    source источник


Ответы (3)


Рекомендуемый подход - проверить "instanceof java.lang.Number" из кода Java - если вы ожидаете значение "числа" JavaScript. После приведения к номеру вы можете преобразовать его в int, long, double, вызвав такие методы, как intValue, longValue и т. д.

person A. Sundararajan    schedule 11.11.2015

Этот код может выполнять преобразование 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}]}}
}
person Igor    schedule 07.08.2017
comment
stackoverflow.com/questions/20512621/ - person Andrew; 04.05.2018
comment
Этот ответ, к сожалению, не работает, потому что его нельзя применить... См. ссылку выше для получения дополнительной информации; Однако можно создавать коллекции Java прямо в JS. - person Andrew; 04.05.2018

Мне очень нравится подход Игоря. Вот его код 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);
  }
}
person J. Brazile    schedule 04.02.2020