castly
Version:
unmarshalling and type-casting in javascript
89 lines (75 loc) • 3.09 kB
JavaScript
var is = exports.typeIs = require('is-a');
exports.verbose = false;
exports.strict = false;
function test(obj, expectedType, name) {
if (is(obj).an(expectedType)) {
return true;
}
if (exports.verbose || exports.strict) {
var message = "(castly) ";
if (name) message += "for " + name + ": ";
message += "expected " + expectedType + ", got " + is(obj).toString() + " instead:";
if (exports.verbose) console.log(message, obj);
if (exports.strict) throw new Error(message);
}
return false;
}
function makeArrayTester(expectedType) {
return function() {
this.describeType = function() {
return function(instance, obj, name) {
for (var attr in obj) instance[attr] = convert(obj[attr], expectedType, name + '["' + attr + '"]');
return instance;
}
}
}
}
function convert(obj, expectedType, name) {
if (is(expectedType).an.Array) {
//list of possible types, if a constructor is in the list and can be used, use it
for (var i = 0; i < expectedType.length; i++) {
if (is(expectedType[i]).a.Function && (is(obj).an.Array || is(obj).an.Object)) return convert(obj, expectedType[i], name);
}
}
if (is(expectedType).a.JsonBasicType) {
//basic json type, just make sure it matches
test(obj, expectedType, name);
return obj;
} else if (!expectedType.is && is(expectedType).a.Function) {
//passed a constructor, make an instance and get it's type description
var instance = new expectedType();
name = name || expectedType.name
if (test(instance.describeType, is.FUNCTION, name + "." + "describeType")) {
var description = instance.describeType();
if (is(description).a.Function) {
//descriptions for non-standard type should be mapping functions
return description(instance, obj, name);
} else {
//standard type descriptions are objects with a type for each expected attribute
for (var attr in description) {
instance[attr] = convert(obj[attr], description[attr], name + "." + attr);
delete obj[attr];
}
//unexpected attributes... set, complain and throw
for (var attr in obj) test(instance[attr] = obj[attr], is.UNDEFINED, name + "." + attr);
}
} else {
//we really *SHOULD* have a type descriptor for this to be useful, but we already threw if you cared
for (attr in obj) instance[attr] = obj[attr];
}
return instance;
}
throw new Error("something bad... " + is(expectedType).toString);
}
function unmarshal(str, type, name) {
if (str) {
return convert(JSON.parse(str), type, name);
} else {
return null;
}
}
exports.test = test;
exports.arrayOf = makeArrayTester;
exports.hashOf = makeArrayTester;
exports.convert = convert;
exports.unmarshal = unmarshal;