JS++ provides toString and fromString (one example) methods in the Standard Library. However, it can be argued that the external type is just as important or even more important in JS++ as string.
We introduce a design pattern for converting complex user-defined JS++ types (such as classes) to JavaScript.
toExternal
You can define a toExternal method that enables you to convert an object of an internal type to external:
import System;
class Point
{
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
function toExternal() {
return {
x: x,
y: y
};
}
}
Point p = new Point(2, 3);
var p2 = p.toExternal(); // conversion to 'external'
Console.log(p2.x); // 2
Console.log(p2.y); // 3
fromExternal
Likewise, you can convert incoming JavaScript data to a complex, user-defined JS++ type:
import System;
import System.Assert;
class Point
{
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
static Point fromExternal(obj) {
assert(typeof obj == "object", "Expected external 'object' type");
assert(typeof obj.x == "number", "Expected incoming external to have numeric 'x' property");
assert(typeof obj.y == "number", "Expected incoming external to have numeric 'y' property");
return new Point(Integer32.fromString(obj.x), Integer32.fromString(obj.y));
}
}
Point p1 = Point.fromExternal({ x: 2, y: 3 });
// Point p2 = Point.fromExternal({ x: "x", y: 3 }); // this will fail
// Point p3 = Point.fromExternal({ x: 2, y: "y" }); // this will fail
Protecting References
For functions, you don’t want to send out a direct reference to external JavaScript. Otherwise, external JavaScript code can modify the JS++ reference in unsafe ways. Therefore, you should wrap the function using closures:
class Foo
{
void bar() {}
function toExternal() {
Foo self = this;
return {
bar: void() {
self.bar();
}
};
}
}
Furthermore, you can use the Standard Library’s System.BoxedExternal to handle this case without wrapping in a closure:
import System;
class Foo
{
BoxedExternal bar;
Foo() {
this.bar = new BoxedExternal(void() {
// ...
});
}
function toExternal() {
return {
bar: this.bar
};
}
}
If the reference to the function accidentally escapes to external, you’ll be alerted by the compiler:
[ ERROR ] JSPPE5000: Cannot convert `System.Dictionary
However, if you actually intended to allow the function reference to escape to external, you can call the unbox method on System.BoxedExternal:
import System;
class Foo
{
BoxedExternal bar;
Foo() {
this.bar = new BoxedExternal(void() {
// ...
});
}
function toExternal() {
return {
bar: this.bar.unbox()
};
}
}
The above code will now compile and the bar function can be passed to external code. However, unlike the code where we wrapped the function in a closure, the external code can now modify the reference to the bar function directly so be careful.
For arrays and containers, you can likewise pass a shallow copy or manually clone each element – depending on the level of trust and safety you desire.
