Skip to content

Instantly share code, notes, and snippets.

@alebianco
Last active August 29, 2015 14:23
Show Gist options
  • Save alebianco/e0a2225203c40846d861 to your computer and use it in GitHub Desktop.
Save alebianco/e0a2225203c40846d861 to your computer and use it in GitHub Desktop.
Validate JSON with typedefs

Problem

I need to validate that the structure of a JSON files matches a set of typedefs.

Solution

Hugely "inspired" by Compiletime of @jasononeil I'm running a macro at compile time that loads the json content, transforms that into an expression and let's the compiler deal with any type mismatch.

Issue 1

Error reporting changed between Haxe 3.1.3 and 3.2.0

When loading faulty.json, that has a string where a boolean is expeted for property "b" at line 6

  • Haxe 3.2.0 identifies the whole parent object as the source of the problem:
test.json:1: lines 1-13 : Data does not match type: Int should be Null<Bool>
test.json:1: lines 1-13 : Int should be Bool
  • Haxe 3.1.3 identifies the actual faulty line as the source of the problem:
test.json:6: characters 5-6 : Data does not match type: Int should be Bool

The reporting of Haxe 3.1.3 would be preferable as more precise of where the error happened.

{
"i":1,
"f":1.0,
"s":"hello",
"b":"true",
"a": [
1, 2, 3
],
"o": {
"foo":"bar"
}
}
import haxe.macro.Context;
import haxe.Json;
import sys.io.File;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.TypeTools;
using haxe.macro.ExprTools;
class Main {
public static function main() {
var foo:Test = validate("valid.json");
}
macro public static function validate(path:String):Expr {
var content = loadFileAsString(path);
var obj = try Json.parse(content) catch (e:Dynamic) {
Context.error('Json from $path failed to validate: $e', Context.currentPos());
}
// can finish it quickly by return macro $v{obj};
var pos:Position = Context.makePosition({min:0, max:0, file:path});
var parsed:Expr = Context.parseInlineString(content, pos);
var type:Null<Type> = Context.getExpectedType();
if (type != null) {
var ct:Null<ComplexType> = type.toComplexType();
var typeable:Expr = macro @:pos(pos) var tmp:$ct = $parsed;
//trace(">> " + typeable);
//trace(">> " + typeable.toString());
try {
Context.typeExpr(typeable);
} catch(err:Dynamic) {
// err is {message:String, pos:Position}
Context.fatalError("Data does not match type: " + err.message, err.pos);
}
}
return macro null;
}
static function loadFileAsString(path:String) {
try {
var p = Context.resolvePath(path);
Context.registerModuleDependency(Context.getLocalModule(),p);
return File.getContent(p);
}
catch(e:Dynamic) {
return Context.error('Failed to load file $path: $e', Context.currentPos());
}
}
}
typedef Test = {
@:optional var i:Int;
@:optional var f:Float;
@:optional var s:String;
@:optional var b:Bool;
@:optional var a:Array<Int>;
@:optional var o:Foo;
}
typedef Foo = {
var foo:String;
}
{
"i":1,
"f":1.0,
"s":"hello",
"b":true,
"a": [
1, 2, 3
],
"o": {
"foo":"bar"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment