Java8 JS Nashorn конвертирует массив в массив Java

Как я могу преобразовать массив JS в собственный массив? В Rhino преобразование выглядело так (код Scala):

val eng = (new javax.script.ScriptEngineManager).getEngineByName("JavaScript")
val obj = eng.eval("[1,2,3,4]")
val arr = obj.asInstanceOf[sun.org.mozilla.javascript.internal.NativeArray]

В Nashorn NativeArray отсутствует, и я не могу найти никакой документации по конвертации.


person user2053898    schedule 18.03.2014    source источник


Ответы (7)


Из Java (и Scala) вы также можете вызвать метод convert в классе jdk.nashorn.api.scripting.ScriptUtils. Например. из Явы:

import jdk.nashorn.api.scripting.ScriptUtils;
...
int[] iarr = (int[])ScriptUtils.convert(arr, int[].class)

мой Scala не слишком беглый, но я считаю, что эквивалент:

val iarr = ScriptUtils.convert(arr, Array[Int]).asInstanceOf(Array[Int])
person Attila Szegedi    schedule 19.03.2014
comment
ошибка: `java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) `code: val eng = (new javax.script.ScriptEngineManager).getEngineByName("JavaScript") val arr = eng.eval([1 ,2,3,4]) println(jdk.nashorn.api.scripting.ScriptUtils.convert(arr, classOf[Array[Byte]]))` - person user2053898; 20.03.2014
comment
У меня проблемы с этим. int[] iarr = (int[])ScriptUtils.convert(engine.eval("[1,2]"), int[].class); бросает java.lang.ClassCastException: Cannot cast jdk.nashorn.api.scripting.ScriptObjectMirror to [I. Я что-то упускаю? Полный пример Java (желательно без использования Java.to) был бы хорош. - person salomvary; 11.12.2014
comment
Это работает и для строк. Мне удалось преобразовать ScriptObjectMirror в массив строк. - person NishM; 07.06.2016

Учитывая массив JavaScript, вы можете преобразовать его в массив Java, используя метод Java.to() в движке oracle nashorn, который доступен в jdk 8.

Пример

var data = [1,2,3,4,5,6];
var JavaArray = Java.to(data,"int[]");
print(JavaArray[0]+JavaArray[1]+JavaArray[2]);
person pardeep131085    schedule 12.05.2014
comment
Я добился успеха в этом. Другой пример двумерного массива: var totals = []; totals.push([50, 100, "Some Text"]); totals.push([75, 54, "More Text"]); Java.to(totals, "java.lang.Object[][]"); - person Gerrit Brink; 05.08.2015

Я нашел решение, которое работает для Rhino и Nashorn.

Первая проблема: сценарист не должен иметь дело с объектами Java!
Это приводит к решению, которое использует только Java.

Во-вторых, он должен работать с Java 8 и предыдущими версиями!

    final ScriptEngineManager manager = new ScriptEngineManager();
    final ScriptEngine engine = manager.getEngineByName("JavaScript");
    try {
        Object result = convert(engine.eval("(function() {return ['a', 'b'];})()"));
        log.debug("Result: {}", result);
        result = convert(engine.eval("(function() {return [3, 7.75];})()"));
        log.debug("Result: {}", result);
        result = convert(engine.eval("(function() {return 'Test';})()"));
        log.debug("Result: {}", result);
        result = convert(engine.eval("(function() {return 7.75;})()"));
        log.debug("Result: {}", result);
        result = convert(engine.eval("(function() {return false;})()"));
        log.debug("Result: {}", result);
    } catch (final ScriptException e) {
        e.printStackTrace();
    }

private static Object convert(final Object obj) {
    log.debug("JAVASCRIPT OBJECT: {}", obj.getClass());
    if (obj instanceof Bindings) {
        try {
            final Class<?> cls = Class.forName("jdk.nashorn.api.scripting.ScriptObjectMirror");
            log.debug("Nashorn detected");
            if (cls.isAssignableFrom(obj.getClass())) {
                final Method isArray = cls.getMethod("isArray");
                final Object result = isArray.invoke(obj);
                if (result != null && result.equals(true)) {
                    final Method values = cls.getMethod("values");
                    final Object vals = values.invoke(obj);
                    if (vals instanceof Collection<?>) {
                        final Collection<?> coll = (Collection<?>) vals;
                        return coll.toArray(new Object[0]);
                    }
                }
            }
        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException
                | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {}
    }
    if (obj instanceof List<?>) {
        final List<?> list = (List<?>) obj;
        return list.toArray(new Object[0]);
    }
    return obj;
}

Rhino предоставляет массивы в виде класса sun.org.mozilla.javascript.internal.NativeArray, реализующего интерфейс java.util.List, с которым легко работать.

13:48:42.400 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class sun.org.mozilla.javascript.internal.NativeArray
13:48:42.405 [main] DEBUG de.test.Tester - Result: [a, b]
13:48:42.407 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class sun.org.mozilla.javascript.internal.NativeArray
13:48:42.407 [main] DEBUG de.test.Tester - Result: [3.0, 7.75]
13:48:42.410 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.String
13:48:42.410 [main] DEBUG de.test.Tester - Result: Test
13:48:42.412 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Double
13:48:42.412 [main] DEBUG de.test.Tester - Result: 7.75
13:48:42.414 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Boolean
13:48:42.415 [main] DEBUG de.test.Tester - Result: false

Nashorn возвращает массив JavaScript как jdk.nashorn.api.scripting.ScriptObjectMirror, который, к сожалению, не реализует интерфейс List.
Я решил эту проблему с помощью отражения, и мне интересно, почему Oracle внесла такое большое изменение.

13:51:02.488 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class jdk.nashorn.api.scripting.ScriptObjectMirror
13:51:02.495 [main] DEBUG de.test.Tester - Nashorn detected
13:51:02.497 [main] DEBUG de.test.Tester - Result: [a, b]
13:51:02.503 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class jdk.nashorn.api.scripting.ScriptObjectMirror
13:51:02.503 [main] DEBUG de.test.Tester - Nashorn detected
13:51:02.503 [main] DEBUG de.test.Tester - Result: [3.0, 7.75]
13:51:02.509 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.String
13:51:02.509 [main] DEBUG de.test.Tester - Result: Test
13:51:02.513 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Double
13:51:02.513 [main] DEBUG de.test.Tester - Result: 7.75
13:51:02.520 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Boolean
13:51:02.520 [main] DEBUG de.test.Tester - Result: false
person Nabor    schedule 08.07.2014

Решением является функция Java.to для преобразования:

engine.eval("Java.to(" + script + ",'byte[]')").asInstanceOf[Array[Byte]]
engine.eval("Java.to(" + name + ",'java.lang.String[]')").asInstanceOf[Array[String]]
person user2053898    schedule 18.03.2014

В случае, если есть массив массивов, мы должны обрабатывать рекурсивно:

public class Nashorn {

public static List<String> fromScript(final Object obj){
    List<String> returnList = new ArrayList<>();

    if(obj==null){
        returnList.add("");
        return returnList;
    }

    if (obj instanceof Bindings) {
        flatArray(returnList, obj);
        return returnList;
    }

    if (obj instanceof List<?>) {
        final List<?> list = (List<?>) obj;
        returnList.addAll(list.stream().map(String::valueOf).collect(Collectors.toList()));
        return returnList;
    }

    if(obj.getClass().isArray()){
        Object[] array = (Object[])obj;
        for (Object anArray : array) {
            returnList.add(String.valueOf(anArray));
        }
        return returnList;
    }

    returnList.add(String.valueOf(obj));
    return returnList;
}

//if we have multiple levels of array, flat the structure
private static void flatArray(List<String> returnList, Object partialArray){
    try {
        final Class<?> cls = Class.forName("jdk.nashorn.api.scripting.ScriptObjectMirror");
        if (cls.isAssignableFrom(partialArray.getClass())) {
            final Method isArray = cls.getMethod("isArray");
            final Object result = isArray.invoke(partialArray);
            if (result != null && result.equals(true)) {
                final Method values = cls.getMethod("values");
                final Object vals = values.invoke(partialArray);
                if (vals instanceof Collection<?>) {
                    final Collection<?> coll = (Collection<?>) vals;
                    for(Object el : coll) {
                        if (cls.isAssignableFrom(el.getClass())) {
                            flatArray(returnList, el);
                        }
                        else{
                            returnList.add(String.valueOf(el));
                        }
                    }
                }
            }
        }
    } catch (ClassNotFoundException | NoSuchMethodException | SecurityException
            | IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) {}
}

}

person Marko Kraljevic    schedule 06.08.2015

Для многомерных массивов:

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

public static Object[] toArray(ScriptObjectMirror scriptObjectMirror)
{
    if (!scriptObjectMirror.isArray())
    {
        throw new IllegalArgumentException("ScriptObjectMirror is no array");
    }

    if (scriptObjectMirror.isEmpty())
    {
        return new Object[0];
    }

    Object[] array = new Object[scriptObjectMirror.size()];

    int i = 0;

    for (Map.Entry<String, Object> entry : scriptObjectMirror.entrySet())
    {
        Object result = entry.getValue();

        System.err.println(result.getClass());

        if (result instanceof ScriptObjectMirror && scriptObjectMirror.isArray())
        {
            array[i] = toArray((ScriptObjectMirror) result);
        }
        else
        {
            array[i] = result;
        }

        i++;
    }

    return array;
}

Теперь используйте метод следующим образом:

ScriptObjectMirror som = (ScriptObjectMirror) YOUR_NASHORN_ENGINE.eval("['this', ['tricky', ['method', ['works']], 'perfectly'], ':)']");
// create multi-dimensional array
Object[] obj = toArray(som);

И вуаля, вы поняли. Ниже приведен пример перебора таких массивов:

public static void print(Object o)
{
    if (o instanceof Object[])
    {
        for (Object ob : (Object[]) o)
        {
            print(ob);
        }
    }
    else
    {
        System.out.println(o);
    }
}
person Ercksen    schedule 01.12.2015

Мне удалось получить массив java/scala с помощью метода, подобного традиционному netscape.javascript.JSObject в Java8/Scala2.12/Nashorn.

val arr = engine.eval(script) match {
  case obj:ScriptObjectMirror =>
    if(obj.isArray){
      for(i <- 0 until obj.size()) yield obj.getSlot(i)
    } else Seq.empty
  case unexpected => Seq.empty
}

Использование ScriptUtil() может получить скалярное значение, такое как String, но, по-видимому, вызовет ClassCastException для массива.

person Takami Torao    schedule 26.11.2017