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.