Tips & Tricks: fromExternal/toExternal Design Pattern

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‘ to `external’ at line 14 char 15

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.