Bring up json object introspection to build models
This commit is contained in:
parent
0e4162af2a
commit
57c181c8b9
|
@ -0,0 +1,59 @@
|
|||
var ModelBuilder = require('./model-builder').ModelBuilder;
|
||||
|
||||
function introspectType(value) {
|
||||
|
||||
// Unknown type, using Any
|
||||
if (value === null || value === undefined) {
|
||||
return ModelBuilder.Any;
|
||||
}
|
||||
|
||||
// Check registered schemaTypes
|
||||
for (var t in ModelBuilder.schemaTypes) {
|
||||
var st = ModelBuilder.schemaTypes[t];
|
||||
if (st !== Object && st !== Array && (value instanceof st)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
var type = typeof value;
|
||||
if (type === 'string' || type === 'number' || type === 'boolean') {
|
||||
return type;
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return 'date';
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
if (value[i] === null || value[i] === undefined) {
|
||||
continue;
|
||||
}
|
||||
var itemType = introspectType(value[i]);
|
||||
if (itemType) {
|
||||
return [itemType];
|
||||
}
|
||||
}
|
||||
return 'array';
|
||||
}
|
||||
|
||||
if (type === 'function') {
|
||||
return value.constructor.name;
|
||||
}
|
||||
|
||||
var properties = {};
|
||||
for (var p in value) {
|
||||
var itemType = introspectType(value[p]);
|
||||
if (itemType) {
|
||||
properties[p] = itemType;
|
||||
}
|
||||
}
|
||||
if(Object.keys(properties).length === 0) {
|
||||
return 'object';
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
module.exports = introspectType;
|
||||
|
||||
|
|
@ -192,21 +192,13 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
if(!DataType) {
|
||||
throw new Error('Invalid type for property ' + attr);
|
||||
}
|
||||
if (Array.isArray(DataType)) {
|
||||
if (Array.isArray(DataType) || DataType === Array) {
|
||||
DataType = List;
|
||||
} else if (DataType.name === 'Date') {
|
||||
var OrigDate = Date;
|
||||
DataType = function Date(arg) {
|
||||
return new OrigDate(arg);
|
||||
};
|
||||
} else if (DataType.name === 'JSON' || DataType === JSON) {
|
||||
DataType = function JSON(s) {
|
||||
return s;
|
||||
};
|
||||
} else if (DataType.name === 'Text' || DataType === ModelBuilder.Text) {
|
||||
DataType = function Text(s) {
|
||||
return s;
|
||||
};
|
||||
} else if(typeof DataType === 'string') {
|
||||
DataType = dataSource.getSchemaType(DataType);
|
||||
}
|
||||
|
@ -271,10 +263,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
function standartize(properties, settings) {
|
||||
Object.keys(properties).forEach(function (key) {
|
||||
var v = properties[key];
|
||||
if (
|
||||
typeof v === 'function' ||
|
||||
typeof v === 'object' && v && v.constructor.name === 'Array'
|
||||
) {
|
||||
if (typeof v === 'function' || Array.isArray(v)) {
|
||||
properties[key] = { type: v };
|
||||
}
|
||||
});
|
||||
|
@ -420,8 +409,9 @@ ModelBuilder.prototype.getSchemaType = function(type) {
|
|||
return this.getSchemaType(type.type);
|
||||
} else {
|
||||
if(!this.anonymousTypesCount) {
|
||||
this.anonymousTypesCount = 1;
|
||||
this.anonymousTypesCount = 0;
|
||||
}
|
||||
this.anonymousTypesCount++;
|
||||
return this.define('AnonymousType' + this.anonymousTypesCount, type, {idInjection: false});
|
||||
/*
|
||||
console.error(type);
|
||||
|
|
30
lib/model.js
30
lib/model.js
|
@ -8,6 +8,7 @@ module.exports = ModelBaseClass;
|
|||
*/
|
||||
|
||||
var util = require('util');
|
||||
var traverse = require('traverse');
|
||||
var jutil = require('./jutil');
|
||||
var List = require('./list');
|
||||
var Hookable = require('./hooks');
|
||||
|
@ -30,6 +31,23 @@ function ModelBaseClass(data) {
|
|||
this._initProperties(data, true);
|
||||
}
|
||||
|
||||
// FIXME: [rfeng] We need to make sure the input data should not be mutated. Disabled cloning for now to get tests passing
|
||||
function clone(data) {
|
||||
/*
|
||||
if(!(data instanceof ModelBaseClass)) {
|
||||
if(data && (Array.isArray(data) || 'object' === typeof data)) {
|
||||
return traverse(data).clone();
|
||||
}
|
||||
}
|
||||
*/
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* Initialize properties
|
||||
* @param data
|
||||
* @param applySetters
|
||||
* @private
|
||||
*/
|
||||
ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
||||
var self = this;
|
||||
var ctor = this.constructor;
|
||||
|
@ -67,13 +85,13 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
|
||||
for (var i in data) {
|
||||
if (i in properties) {
|
||||
this.__data[i] = this.__dataWas[i] = data[i];
|
||||
this.__data[i] = this.__dataWas[i] = clone(data[i]);
|
||||
} else if (i in ctor.relations) {
|
||||
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
|
||||
this.__cachedRelations[i] = data[i];
|
||||
} else {
|
||||
if(strict === false) {
|
||||
this.__data[i] = this.__dataWas[i] = data[i];
|
||||
this.__data[i] = this.__dataWas[i] = clone(data[i]);
|
||||
} else if(strict === 'throw') {
|
||||
throw new Error('Unknown property: ' + i);
|
||||
}
|
||||
|
@ -83,7 +101,7 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
if (applySetters === true) {
|
||||
Object.keys(data).forEach(function (attr) {
|
||||
if((attr in properties) || (attr in ctor.relations) || strict === false) {
|
||||
self[attr] = data[attr];
|
||||
self[attr] = self.__data[attr] || data[attr];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -110,8 +128,10 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
self.__data[attr] = String(self.__data[attr]);
|
||||
}
|
||||
}
|
||||
if (type.name === 'Array' || typeof type === 'object' && type.constructor.name === 'Array') {
|
||||
self.__data[attr] = new List(self.__data[attr], type, self);
|
||||
if (type.name === 'Array' || Array.isArray(type)) {
|
||||
if(!(self.__data[attr] instanceof List)) {
|
||||
self.__data[attr] = new List(self.__data[attr], type, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module.exports = function (Types) {
|
|||
*/
|
||||
Types.Text = function Text(value) {
|
||||
if (!(this instanceof Text)) {
|
||||
return new Text(value);
|
||||
return value;
|
||||
}
|
||||
this.value = value;
|
||||
}; // Text type
|
||||
|
@ -19,7 +19,7 @@ module.exports = function (Types) {
|
|||
|
||||
Types.JSON = function JSON(value) {
|
||||
if (!(this instanceof JSON)) {
|
||||
return new JSON(value);
|
||||
return value;
|
||||
}
|
||||
this.value = value;
|
||||
}; // JSON Object
|
||||
|
@ -29,7 +29,7 @@ module.exports = function (Types) {
|
|||
|
||||
Types.Any = function Any(value) {
|
||||
if (!(this instanceof Any)) {
|
||||
return new Any(value);
|
||||
return value;
|
||||
}
|
||||
this.value = value;
|
||||
}; // Any Type
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"async": "latest",
|
||||
"inflection": "~1.2.6"
|
||||
"inflection": "~1.2.6",
|
||||
"traverse": "latest"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
var assert = require('assert');
|
||||
var ModelBuilder = require('../lib/model-builder').ModelBuilder;
|
||||
var introspectType = require('../lib/introspection');
|
||||
var traverse = require('traverse');
|
||||
|
||||
describe('Introspection of model definitions from JSON', function() {
|
||||
|
||||
it('should handle simple types', function() {
|
||||
assert.equal(introspectType('123'), 'string');
|
||||
assert.equal(introspectType(true), 'boolean');
|
||||
assert.equal(introspectType(false), 'boolean');
|
||||
assert.equal(introspectType(12), 'number');
|
||||
assert.equal(introspectType(new Date()), 'date');
|
||||
});
|
||||
|
||||
it('should handle array types', function() {
|
||||
var type = introspectType(['123']);
|
||||
assert.deepEqual(type, ['string'], 'type should be ["string"]');
|
||||
type = introspectType([1]);
|
||||
assert.deepEqual(type, ['number'], 'type should be ["number"]');
|
||||
// Stop at first known type
|
||||
type = introspectType([1, '123']);
|
||||
assert.deepEqual(type, ['number'], 'type should be ["number"]');
|
||||
type = introspectType([null, '123']);
|
||||
assert.deepEqual(type, ['string'], 'type should be ["string"]');
|
||||
|
||||
type = introspectType([]);
|
||||
assert.equal(type, 'array');
|
||||
});
|
||||
|
||||
it('should return Any for null or undefined', function() {
|
||||
assert.equal(introspectType(null), ModelBuilder.Any);
|
||||
assert.equal(introspectType(undefined), ModelBuilder.Any);
|
||||
});
|
||||
|
||||
it('should return a schema for object', function() {
|
||||
var json = {a: 'str', b: 0, c: true};
|
||||
var type = introspectType(json);
|
||||
assert.equal(type.a, 'string');
|
||||
assert.equal(type.b, 'number');
|
||||
assert.equal(type.c, 'boolean');
|
||||
});
|
||||
|
||||
it('should handle nesting objects', function() {
|
||||
var json = {a: 'str', b: 0, c: true, d: {x: 10, y: 5}};
|
||||
var type = introspectType(json);
|
||||
assert.equal(type.a, 'string');
|
||||
assert.equal(type.b, 'number');
|
||||
assert.equal(type.c, 'boolean');
|
||||
assert.equal(type.d.x, 'number');
|
||||
assert.equal(type.d.y, 'number');
|
||||
});
|
||||
|
||||
it('should handle nesting arrays', function() {
|
||||
var json = {a: 'str', b: 0, c: true, d: [1, 2]};
|
||||
var type = introspectType(json);
|
||||
assert.equal(type.a, 'string');
|
||||
assert.equal(type.b, 'number');
|
||||
assert.equal(type.c, 'boolean');
|
||||
assert.deepEqual(type.d, ['number']);
|
||||
});
|
||||
|
||||
it('should build a model from the introspected schema', function(done) {
|
||||
|
||||
var json = {
|
||||
name: 'Joe',
|
||||
age: 30,
|
||||
birthday: new Date(),
|
||||
vip: true,
|
||||
address: {
|
||||
street: '1 Main St',
|
||||
city: 'San Jose',
|
||||
state: 'CA',
|
||||
zipcode: '95131',
|
||||
country: 'US'
|
||||
},
|
||||
friends: ['John', 'Mary'],
|
||||
emails: [
|
||||
{label: 'work', id: 'x@sample.com'},
|
||||
{label: 'home', id: 'x@home.com'}
|
||||
],
|
||||
tags: []
|
||||
};
|
||||
|
||||
var copy = traverse(json).clone();
|
||||
|
||||
var schema = introspectType(json);
|
||||
|
||||
var builder = new ModelBuilder();
|
||||
var Model = builder.define('MyModel', schema, {idInjection: false});
|
||||
|
||||
// FIXME: [rfeng] The constructor mutates the arguments
|
||||
var obj = new Model(json);
|
||||
|
||||
obj = obj.toObject();
|
||||
|
||||
assert.deepEqual(obj, copy);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue