Class JSObject

java.lang.Object
org.graalvm.webimage.api.JSValue
org.graalvm.webimage.api.JSObject

public class JSObject extends JSValue
A JSObject is a Java-side wrapper for JavaScript objects. The purpose of this class is that a JavaScript object is not normally an instance of any Java class, and it therefore cannot be represented as a data-type in Java programs. When the JavaScript code (invoked by the method annotated with the JS annotation) returns a JavaScript object, that object gets wrapped into a JSObject instance. The JSObject allows the Java code to access the fields of the underlying JavaScript object using the get and set methods. The Java JSObject instance that corresponds to the JavaScript object is called a Java mirror. Vice versa, the JavaScript instance is a JavaScript mirror for that JSObject instance.

Anonymous JavaScript objects

Here are a few examples of creating and modifying anonymous JavaScript objects:
@JS("return {x: x, y: y};")
public static native JSObject createPair(double x, double y);

JSObject pair = createPair(3.2, 4.8);

System.out.println(pair.get("x"));
System.out.println(pair.get("y"));
In this example, using the JSObject methods, the user can access the x and y fields.
pair.set("x", 5.4);
System.out.println(pair.get("x"));
The code above sets a new value for the x field, and then prints the new value.

Anonymous JavaScript functions

A JSObject can be a Java-side wrapper for a JavaScript Function object. The JavaScript Function value can be returned by the JavaScript code of the method annotated with the JS annotation. The Java program can then call the underlying function by calling the call(Object, Object...) method. If the underlying JavaScript object is not callable, then calling call will result in an exception. The call method has the following signature:
public Object call(Object thisArgument, Object... arguments);
The invoke method has the following signature:
public Object invoke(Object... arguments);
The difference is that the method call takes an object for specifying this of the JavaScript function, while invoke uses the underlying JSObject as the value of this.

Here is an example of how to use JSObject as a function:

public static void main(String[] args) {
    JSObject function = createFunction();
    Object result = function.invoke(JSNumber.of(1), JSNumber.of(2));
    System.out.println(result);
}

@JS("return (a, b) => { return a + b; };")
public static native JSObject createFunction();

The JS-annotated method createFunction in the preceding example creates a function in JavaScript that adds together two numbers. The call to createFunction returns a JSObject object, upon which users can call the invoke method to execute the underlying JavaScript function. The result JavaScript<number; 3< is then printed.

Declaring JavaScript classes in Java

The second purpose of JSObject is to declare JavaScript classes as classes in Java code, in a way that makes the Java objects look-and-feel like native JavaScript objects. Users should subclass the JSObject class when they need to define a JavaScript class whose fields and methods can be accessed conveniently from Java, without the get(Object) and set(Object, Object) methods. Directly exposing a Java object to JavaScript code means that the JavaScript code is able to manipulate the data within the object (e.g. mutate fields, add new fields, or redefine existing fields), which is not allowed by default for regular Java classes. Extending JSObject furthermore allows the JavaScript code to instantiate objects of the JSObject subclass. One of the use-cases for these functionalities are JavaScript frameworks that redefine properties of JavaScript objects with custom getters and setters, with the goal of enabling data-binding or reactive updates. In a subclass of JSObject, every JavaScript property directly corresponds to the Java field of the same name. Consequently, all these properties point to native JavaScript values rather than Java values, so bridge methods are generated that are called for each property access and that convert native JavaScript values to their Java counterparts. The conversion rules are the same as in a JS-annotated method. Furthermore, note that JavaScript code can violate the Java type-safety by storing into some property a value that is not compatible with the corresponding Java field. For this reason, the bridge methods also generate check-casts on every access: if the JavaScript property that corresponds to the Java field does not contain a compatible value, a ClassCastException is thrown. There are several restrictions imposed on JSObject subclasses:
  • Only public and protected fields are allowed to ensure encapsulation.
  • Instance fields must not be final. This restriction ensures that JavaScript code cannot inadvertently change the property that corresponds to a final field.
Example: consider the following JSObject subclass:
public class Point extends JSObject {
    protected double x;
    protected double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double absolute() {
        return Math.sqrt(x * x + y * y);
    }
}
The preceding Java class is effectively translated to the corresponding JavaScript class:
class Point {
    constructor(x, y){
        this.x=x;
        this.y=y;
    }

    absolute() {
        // Java code that computes sqrt(x * x + y * y);
    }
}
The Point class can be used from Java as if it were a normal Java class:
Point p = new Point(0.3, 0.4);
System.out.println(p.x);
System.out.println(p.y);
System.out.println(p.absolute());
All accesses to the fields x and y are rewritten to accesses on the corresponding JavaScript properties. JavaScript code may assign values to these properties that violate the type of the corresponding Java fields, but a subsequent Java read of such a field will result in a ClassCastException.
@JS("p.x = s;")
public static native void corrupt(Point p, String s);

corrupt(p, "whoops");
System.out.println(p.x); // Throws a ClassCastException.

Passing JSObject subclasses between JavaScript and Java

When an object of the JSObject subclass is passed from Java to JavaScript code using the JS annotation, the object is converted from its Java representation to its JavaScript representation. Changes in the JavaScript representation are reflected in the Java representation and vice-versa. Example: the following code resets the Point object in JavaScript by calling the reset method, and then prints 0, 0:
@JS("p.x = x; p.y = y;")
public static native void reset(Point p, double x, double y);

Point p0 = new Point(1.5, 2.5);
reset(p0, 0.0, 0.0);
System.out.println(p0.x + ", " + p0.y);
A Class object that represents JSObject can also be passed to JavaScript code. The Class object is wrapped in a proxy, which can be used inside a new expression to instantiate the object of the corresponding class from JavaScript. Example: the following code creates a Point object in JavaScript:
@JS("return new pointType(x, y);")
public static Point create(Class pointType, double x, double y);

Point p1 = create(Point.class, 1.25, 0.5);
System.out.println(p1.x + ", " + p1.y);
Note that creating an object in JavaScript and passing it to Java several times does not guarantee that the same mirror instance is returned each time -- each time a JavaScript object becomes a Java mirror, a different instance of the mirror may be returned.

Importing existing JavaScript classes

The JSObject class allows access to properties of any JavaScript object using the get(Object) and set(Object, Object) methods. In situations where the programmer knows the relevant properties of a JavaScript object (for example, when there is a class corresponding to the JavaScript object), the object's "blueprint" can be "imported" to Java. To do this, the user declares a JSObject subclass that serves as a facade to the JavaScript object. This subclass must be annotated with the JS.Import annotation. The name of the declared class Java must match the name of the JavaScript class that is being imported. The package name of the Java class is not taken into account -- the same JavaScript class can be imported multiple times from within separate packages. The exposed JavaScript fields must be declared as protected or public. The constructor parameters must match those of the JavaScript class, and its body must be empty. Here is an example of a class declared in JavaScript:
class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }
}
To import this class in Java code, we need the following declaration in Java:
@JS.Import
public class Rectangle extends JSObject {
    protected int width;
    protected int height;

    public Rectangle(int width, int height) {
    }
}
The fields declared in the Rectangle class are directly mapped to the properties of the underlying JavaScript object. If the type of the property of the underlying JavaScript object does not match the type of the field declared in Java, then a field-read in Java will throw a ClassCastException. The Rectangle class can be instantiated from Java as follows:
Rectangle r = new Rectangle(640, 480);
System.out.println(r.width + "x" + r.height);
A JavaScript object whose constructor property matches the imported JavaScript class can be converted to the declared Java class when the JavaScript code passes a value to Java. Here is a code example that creates the Rectangle object in JavaScript, and passes it to Java:
@JS("return new Rectangle(width, height);")
Rectangle createRectangle(int width, int height);
Another way to convert a JavaScript object to a Java facade is to call the as(Class) method to cast the JSObject instance to the proper subtype.

Exporting Java classes to JavaScript

The users can annotate the exported classes with the JS.Export annotation to denote that the JSObject subclass should be made available to JavaScript code. Exported classes can be accessed using the JavaScript VM-instance API, using the `exports` property. Example: the following code exports a Java class:
package org.example;

@JS.Export
public class Randomizer extends JSObject {
    private Random rng = new Random(719513L);

    public byte[] randomBytes(int length) {
        byte[] bytes = new byte[length];
        rng.nextBytes(bytes);
        return bytes;
    }
}
The exported class can then be used from JavaScript code as follows:
GraalVM.run([]).then(vm => {
  const Randomizer = vm.exports.org.example.Randomizer;
  const r = new Randomizer();
  const bytes = r.randomBytes(1024);
});
  • Constructor Details

    • JSObject

      protected JSObject()
  • Method Details

    • create

      public static JSObject create()
      Creates an empty JavaScript object.
      Returns:
      an empty JavaScript object
    • typeofString

      public JSString typeofString()
    • typeof

      public String typeof()
      Specified by:
      typeof in class JSValue
    • stringValue

      protected String stringValue()
      Specified by:
      stringValue in class JSValue
    • get

      public Object get(Object key)
      Returns the value of the key passed as the argument in the JavaScript object.
      Parameters:
      key - the object under which the returned value is placed in the JavaScript object
      Returns:
      the value of the key passed as the argument in the JavaScript object
    • set

      public void set(Object key, Object newValue)
      Sets the value of the key passed as the argument in the JavaScript object.
      Parameters:
      key - the object under which the value should be placed in the JavaScript object
      newValue - the value that should be placed under the given key in the JavaScript object
    • keys

      public Object keys()
      Returns the array of property keys for this object.
      Returns:
      an array of all the keys that can be used with get on this object
    • invoke

      public Object invoke(Object... args)
      Invoke the underlying JavaScript function, if this object is callable.
      Parameters:
      args - The array of Java arguments, which is converted to JavaScript and passed to the underlying JavaScript function
      Returns:
      The result of the JavaScript function, converted to the corresponding Java value
    • call

      public Object call(Object thisArg, Object... args)
      Calls the underlying JavaScript function with the given value for the binding of this in the function, if this object is callable.
      Parameters:
      thisArg - The value for the binding of this inside the JavaScript function
      args - The array of Java arguments, which is converted to JavaScript and passed to the underlying JavaScript function
      Returns:
      The result of the JavaScript function, converted to the corresponding Java value
    • asBooleanArray

      public boolean[] asBooleanArray()
      Overrides:
      asBooleanArray in class JSValue
    • asByteArray

      public byte[] asByteArray()
      Overrides:
      asByteArray in class JSValue
    • asShortArray

      public short[] asShortArray()
      Overrides:
      asShortArray in class JSValue
    • asCharArray

      public char[] asCharArray()
      Overrides:
      asCharArray in class JSValue
    • asIntArray

      public int[] asIntArray()
      Overrides:
      asIntArray in class JSValue
    • asFloatArray

      public float[] asFloatArray()
      Overrides:
      asFloatArray in class JSValue
    • asLongArray

      public long[] asLongArray()
      Overrides:
      asLongArray in class JSValue
    • asDoubleArray

      public double[] asDoubleArray()
      Overrides:
      asDoubleArray in class JSValue
    • as

      public <T> T as(Class<T> cls)
      Overrides:
      as in class JSValue
    • equalsJavaScript

      public boolean equalsJavaScript(JSObject that)