nadako's rants

Haxe: working with dynamic anonymous structures in a typed way

Often people ask about how or what's the best way to work with anonymous structures that don't have a predefined set of fields (and hence can't be typed with haxe structure type). Basically, objects like that are string-keyed dictionaries. They can be used instead of haxe's Map type for example when working with JSON collections or extern JavaScript data.

Haxe provides no specific type representing this concept out-of-the-box and one has to work with these objects using Dynamic type and Reflect methods. It is no good, because of the loss of strict typing, but fortunately it is possible to wrap all dynamic affairs in a properly-typed abstract type that is easy-to work with and has no run-time overhead over Dynamic+Reflect option.

In my code, I use the following abstract type that provides Map-like API over dynamic structures by wrapping Reflect calls:

abstract DynamicObject<T>(Dynamic<T>) from Dynamic<T> {

    public inline function new() {
        this = {};
    }

    @:arrayAccess
    public inline function set(key:String, value:T):Void {
        Reflect.setField(this, key, value);
    }

    @:arrayAccess
    public inline function get(key:String):Null<T> {
        #if js
        return untyped this[key];
        #else
        return Reflect.field(this, key);
        #end
    }

    public inline function exists(key:String):Bool {
        return Reflect.hasField(this, key);
    }

    public inline function remove(key:String):Bool {
        return Reflect.deleteField(this, key);
    }

    public inline function keys():Array<String> {
        return Reflect.fields(this);
    }
}

Let's see how its usage looks like and what it generates:

class Main {
    static function main() {
        var a:DynamicObject<Int> = {field1: 1, field2: 2}; // implicit cast supported by "from Dynamic<T>"

        var f = "field3";
        if (!a.exists(f))
            a[f] = 3;

        for (key in a.keys()) {
            trace(a[key]);
            a.remove(key);
        }
    }
}

Here's what generated JS looks like for the main function:

Main.main = function() {
	var a = { field1 : 1, field2 : 2};
	var f = "field3";
	if(!Object.prototype.hasOwnProperty.call(a,f)) a[f] = 3;
	var _g = 0;
	var _g1 = Reflect.fields(a);
	while(_g < _g1.length) {
		var key = _g1[_g];
		++_g;
		console.log(a[key]);
		Reflect.deleteField(a,key);
	}
};

As you can see it's pretty the same as if you've worked with Dynamic and Reflect by hand, but it's much easier and strictly typed.

Now I only hope Google will give this post for an answer on working with dynamic structures in Haxe :-P