Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// Experimenting with custom JSON serialization.
// Things that annoy me in existing implementation are:
// * Ordering: Since haxe.Json takes dynamic objects, field order cannot be warranted.
// If resulting JSON has to be viewed/edited manually, uncertain order is no help.
// * Readability: You have no control over how contents are written. Prettyprint gets bulky fast.
// * Typing: JSON nodes are, by definition, dynamic objects, which means that you'll most
// likely have to implement an extra I/O layer with "data from class N" typedefs.
// * Efficiency: Dynamic objects and arrays are read only to (most often) be thrown again later.
//
// So here I'm working on a library that solves a fair of these problems by providing
// an interface for class serialization that allows JSON values to be received directly by
// according class method. This reduces amounts of garbage generated and eliminates the need for
// "intermediate" typedefs completely.
// Typing is currently supported via non-standard `Type({ ... })` syntax, with resolving handled by
// calling serializable class. Might switch to Type API later, but generally this allows extra
// security (i.e. not allowing class instantiation unless needed in context).
// Printing can be controlled via method flags and typed sub-objects (subject of change).
//
// Example of use and output code below. What do you think?
// Main class:
class SkyHx implements Jsonizeable {
public static inline var P_CONF = "skyhx.json";
// ...
public var nextX:Int;
public var nextY:Int;
public var sectorPool:Array<SectorAt>;
// ...
function new() {
// ...
// Loading:
try {
JsonTools.loadFile(this, P_CONF);
} catch (e:Dynamic) {
trace('Failed to load $P_CONF: $e');
throw e;
}
// Saving:
try {
JsonTools.saveFile(this, P_CONF);
} catch (e:Dynamic) {
trace('Failed to save $P_CONF: $e');
}
//
trace(sectorPool);
}
//{ I/O
public function save(q:JsonWriter, f:Int):Void {
q.saveNumber("nextX", nextX);
q.saveNumber("nextY", nextY);
q.saveArray("sectorPool", sectorPool, ~1);
}
public function loadStart():Void {
nextX = nextY = 0;
sectorPool = [];
}
public function loadNumber(k:String, v:Float):Void {
switch (k) {
case "nextX": nextX = Std.int(v);
case "nextY": nextY = Std.int(v);
}
}
public function loadString(k:String, v:String):Void { }
public function loadArray(k:String, v:Array<Dynamic>):Void {
switch (k) {
case "sectorPool": sectorPool = cast v;
}
}
public function loadHash(k:String, v:Hashtable<String, Dynamic>):Void { }
public function loadObject(k:String, v:Jsonizeable):Void { }
public function saveType():String return null;
public function loadAlloc(type:String):Jsonizeable {
return switch (type) {
case "SectorAt": new SectorAt();
default: null;
}
}
public function loadEnd():Void { }
//}
}
// SectorAt:
class SectorAt implements Jsonizeable {
public var x:Int;
public var y:Int;
public function new() { }
public function toString() return 'SectorAt($x, $y)';
//
public function save(q:JsonWriter, f:Int):Void {
q.saveNumber("x", x);
q.saveNumber("y", y);
}
public function saveType():String return "SectorAt";
//
public function loadStart():Void { x = y = 0; }
public function loadNumber(k:String, v:Float) switch(k) {
case "x": x = Std.int(v);
case "y": y = Std.int(v);
}
public function loadString(k:String, v:String):Void { }
public function loadArray(k:String, v:Array<Dynamic>):Void { }
public function loadHash(k:String, v:Hashtable<String, Dynamic>):Void { }
public function loadObject(k:String, v:Jsonizeable):Void { }
public function loadAlloc(t:String):Jsonizeable return null;
public function loadEnd():Void { }
}
// Input file:
{
nextX: 4,
nextY: 3,
sectorPool: [ SectorAt({x: 2,y: 0}),SectorAt({x: 3,y: 0})],
}
// Output log:
[SectorAt(2, 0),SectorAt(3, 0)]
// Output file:
{
nextX: 4,
nextY: 3,
sectorPool: [
SectorAt({ x: 2, y: 0, }),
SectorAt({ x: 3, y: 0, }),
],
}
// Third argument in saveArray/Hash/Object is a set of bit flags, each determining
// whether next N-th depth should be printed in normal (0) or compact (1) mode.
// Changing it to 0, for example, will yield multi-line SectorAt definitions:
{
nextX: 4,
nextY: 3,
sectorPool: [
SectorAt({
x: 2,
y: 0,
}),
SectorAt({
x: 3,
y: 0,
}),
],
}
@jarnik
Copy link

jarnik commented Sep 28, 2014

Great idea! A couple ideas:
Are the "load*" methods a mandatory part of the Jsonizeable interface? Would you do need to implement them in every serializable class?
Maybe the Reflect.setField(...) could be used to deserialize the properties (parsing basic variable types, allowing custom parser for more complex ones). You could also specify a subset of properties that can be de/serialized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment