Designed to enforce schema on Javascript objects. Allows you to specify type, transformation and validation of values via a set of attributes. Support for sub-schemas included.
npm install schema-object
bower install schema-object
TypeScript typings included. Node and browser environments supported.
For older versions of Node, run node with the harmony proxies --harmony_proxies
flag. This flag is on by default in newer NodeJS versions.
Very basic usage example
var SchemaObject = ; // Create User schemavar User = firstName: String lastName: String birthDate: Date; // Initialize instance of uservar user = firstName: 'Scott' lastName: 'Hovestadt' birthDate: 'June 21, 1988';console; // Prints: firstName: 'Scott' lastName: 'Hovestadt' birthDate: Tue Jun 21 1988 00:00:00 GMT-
Advanced example
var SchemaObject = ; // Create custom basic type// Type can be extended with more properties when definedvar NotEmptyString = type: String minLength: 1; // Create sub-schema for user's Companyvar Company = // Any string will be magically parsed into date startDate: Date endDate: Date // String with properties name: NotEmptyString // Typed array tags: NotEmptyString; // Create User schemavar User = // Basic user information using properties firstName: NotEmptyString lastName: NotEmptyString // Extend "NotEmptyString" with enum property gender: type: NotEmptyString enum: 'm' 'f' // Sub-object with enforced type work: Company // Add methods to User prototype methods: { return thisfirstName + ' ' + thislastName; } ; // Create Account schema by extending User schemavar Account = User; // Initialize a new instance of the User with a valuevar account = username: 'scotthovestadt' password: 'hunter2' firstName: 'Scott' lastName: 'Hovestadt' gender: 'm' work: name: 'My Company' startDate: 'June 1, 2010' ; console; // Prints:"scotthovestadt" console; // Prints: firstName: 'Scott' lastName: 'Hovestadt' gender: 'm' work: startDate: Tue Jun 01 2010 00:00:00 GMT- name: 'My Company' username: 'scotthovestadt'
Static Methods
extend
Allows you to extend SchemaObject instance schema and options.
var Person = firstName: String lastName: String constructors: { fullName = fullName; thisfirstName = fullName0; thislastName = fullName1; } methods: { return thisfirstName + ' ' + thislastName; } ; var Employee = Person; var john = Employee;johnid = 1; console; // Prints:"[Employee ID 1] John Smith"
Methods
clone
Clones SchemaObject and all sub-objects and sub-arrays into another SchemaObject container. Writes on any sub-objects or sub-arrays will not touch the original.
var User = firstName: String lastName: String; var user = firstName: 'Scott' lastName: 'Hovestadt'; var anotherUser = user;anotherUserfirstName = 'John';anotherUserlastName = 'Smith'; console;console; // Prints: firstName: 'Scott' lastName: 'Hovestadt' firstName: 'John' lastName: 'Smith'
toObject
toObject returns a cloned primitive object, stripped of all magic. Writes on any sub-objects or sub-arrays will not touch the original. All values will be typecasted and transformed, but future writes to the primitive object will not. The invisible attribute can be used to ensure an index stored on the SchemaObject will not be written to the primitive object. toObject is automatically called if a SchemaObject is passed to JSON.stringify.
var User = firstName: String lastName: String birthDate: Date; var user = firstName: 'Scott' lastName: 'Hovestadt' birthDate: 'June 21, 1988';console; // Prints: firstName: 'Scott' lastName: 'Hovestadt' birthDate: Tue Jun 21 1988 00:00:00 GMT-
populate
populate will copy an object's values.
var User = firstName: String lastName: String; var user = ;user;console; // Prints: firstName: 'Scott' lastName: 'Hovestadt'
clear
clear removes all values.
var User = firstName: String lastName: String; var user = firstName: 'Scott' lastName: 'Hovestadt';console; // Prints: firstName: 'Scott' lastName: 'Hovestadt' userclear;console; // Prints: firstName: undefined lastName: undefined
isErrors / getErrors / clearErrors
See documentation on Errors.
Options
When you create the SchemaObject, you may pass a set of options as a second argument. These options allow you to fine-tune the behavior of your objects for specific needs.
constructors
The constructors option allows you to override the default or attach new constructors to your SchemaObject-created class.
var Person = firstName: String lastName: String constructors: // Override default constructor { // Will call this.populate this; // Populate default values with custom constructor ifthisfirstName === undefined thisfirstName = 'John'; ifthislastName === undefined thislastName = 'Smith'; } // Create new constructor used by calling Person.fromFullName { // Will call default constructor this; fullName = fullName; iffullName0 thisfirstName = fullName0; iffullName1 thislastName = fullName1; } ; var person = firstName: 'Scott' ;// ORvar person = Person; console; // Prints: firstName: 'Scott' lastName: 'Smith'
methods
The methods option allows you to attach new methods to your SchemaObject-created class.
var Person = firstName: String lastName: String methods: { return thisfirstName + ' ' + thislastName; } ; var person = firstName: 'Scott' lastName: 'Hovestadt' ;console; // Prints: 'Scott Hovestadt'
toObject(object)
toObject allows you to transform the model before the result of toObject() is passed back.
This example shows how it could be used to ensure transform all strings to uppercase.
var Model = string: String { _; return object; }; var model = ;modelstring = 'a string';console; // Prints: 'a string' var simpleObject = model;console; // Prints: 'A STRING'
inheritRootThis
inheritRootThis (default: false) should be set to true if you want your nested SchemaObjects to have the "this" context of the root SchemaObject. SchemaObjects created with the shorthand syntax are considered a part of the parent object and have this enabled automatically.
setUndefined
setUndefined (default: false) allows you to specify if an unset value is written when toObject() is called. By default, the behavior is not to write unset values. This means if there is a null/undefined primitive, an empty array, or an empty object it will not be written to the object when toObject() is called.
This value should set to true if:
- You want your database (Mongo, etc) to write unset indexes and overwrite existing fields with empty values.
- You want to write undefined values when exporting to JSON explicitly.
- You want toObject() to contain empty arrays and objects.
preserveNull
preserveNull (default: false) allows you to set null
to any field. The default behavior will treat null as unsetting the field.
This value should set to true if you're intentionally using null and know the difference between null and undefined.
strict
strict (default: true) allows you to specify what happens when an index is set on your SchemaObject that does not exist in the schema. If strict mode is on, the index will be ignored. If strict mode is off, the index will automatically be created in the schema when it's set with type "any".
With strict mode on (default):
var Profile = id: String strict: true; var profile = ;profileid = 'abc123';profilecustomField = 'hello'; // Prints: id: 'abc123'
With strict mode off:
var Profile = id: String strict: false; var profile = ;profileid = 'abc123';profilecustomField = 'hello'; // Prints: id: 'abc123' customField: 'hello'
dotNotation
dotNotation (default: false) allows you to access deep fields in child objects using dot notation. If dot notation is on, getting or setting "profile.name" will look inside the object for a child object "profile" and then for key "name", instead of simply setting the index "profile.name" on the parent object.
The following example turns off strict mode to demonstrate the differences when toggling dot notation on or off, although dot notation can be used with or without strict mode.
With dot notation off (default):
var User = dotNotation: false strict: false; var user = ;user'profile.name' = 'Scott'; // Prints: 'profile.name': 'Scott'
With dot notation on:
var User = dotNotation: true strict: false; var user = ;user'profile.name' = 'Scott'; // Prints: profile: name: 'Scott'
keysIgnoreCase
keysIgnoreCase (default: false) allows you to set indexes without worrying about the casing of the key.
With keys ignore case off (default):
var User = firstName: String keyIgnoreCase: false; var user = ;userfirstname = 'Scott'; // Prints:{}
With keys ignore case on:
var User = firstName: String keyIgnoreCase: true; var user = ;userfirstname = 'Scott'; // Prints: firstName: 'Scott'
onBeforeValueSet(value, key) / onValueSet(value, key)
onBeforeValueSet / onValueSet allow you to bind an event handler to all write operations on an object. Currently, it will only notify of write operations on the object itself and will not notify you when child objects are written to. If you return false or throw an error within the onBeforeValueSet handler, the write operation will be cancelled. Throwing an error will add the error to the error stack.
var User = name: String { ifkey === 'name' && value === 'Scott' return false; }; var user = ; username = 'Scott';// Prints: name: undefined username = 'Scott Hovestadt';// Prints: name: 'Scott Hovestadt'
Errors
When setting a value fails, an error is generated silently. Errors can be retrieved with getErrors() and cleared with clearErrors().
var Profile = id: type: String minLength: 5; var profile = ;profileid = '1234'; console; // Prints:true console; // Prints: errorMessage: 'String length too short to meet minLength requirement.' setValue: '1234' originalValue: undefined fieldSchema: name: 'id' type: 'string' minLength: 5 // Clear all errors.profile;
Types
Supported types:
- String
- Number
- Boolean
- Date
- Array (including types within Array)
- Object (including typed SchemaObjects for sub-schemas)
- 'alias'
- 'any'
When a type is specified, it will be enforced. Typecasting is enforced on all types except 'any'. If a value cannot be typecasted to the correct type, the original value will remain untouched.
Types can be extended with a variety of attributes. Some attributes are type-specific and some apply to all types.
Custom types can be created by defining an object with type properties.
var NotEmptyString = type: String minLength: 1;country: type: NotEmptyString default: 'USA'
General attributes
transform
Called immediately when value is set and before any typecast is done.
name: type: String { // Modify the value here... return value;}
default
Provide default value. You may pass value directly or pass a function which will be executed when the object is initialized. The function is executed in the context of the object and can use "this" to access other properties (which .
country: type: String default: 'USA'
getter
Provide function to transform value when retrieved. Executed in the context of the object and can use "this" to access properties.
string: type: String { return value; }
required
If true, a value must be provided. If a value is not provided, an error will be generated silently. If used in conjunction with default, this check will always pass.
fullName: type: String required: true
readOnly
If true, the value can be read but cannot be written to. This can be useful for creating fields that reflect other values.
fullName: type: String readOnly: true { return thisfirstName + ' ' + thislastName;}
invisible
If true, the value can be written to but isn't outputted as an index when toObject() is called. This can be useful for creating aliases that redirect to other indexes but aren't actually present on the object.
zip: StringpostalCode: type: 'alias' invisible: true index: 'zip'// this.postalCode = 12345 -> this.toObject() -> {zip: '12345'}
String
stringTransform
Called after value is typecast to string if value was successfully typecast but called before all validation.
postalCode: type: String { // Type will ALWAYS be String, so using string prototype is OK. return string;}
regex
Validates string against Regular Expression. If string doesn't match, it's rejected.
memberCode: type: String regex: '^([0-9A-Z]{4})$'
enum
Validates string against array of strings. If not present, it's rejected.
gender: type: String enum: 'm' 'f'
minLength
Enforces minimum string length.
notEmpty: type: String minLength: 1
maxLength
Enforces maximum string length.
stateAbbrev: type: String maxLength: 2
clip
If true, clips string to maximum string length instead of rejecting string.
bio: type: String maxLength: 255 clip: true
Number
min
Number must be > min attribute or it's rejected.
positive: type: Number min: 0
max
Number must be < max attribute or it's rejected.
negative: type: Number max: 0
Array
arrayType
Elements within the array will be typed to the attributes defined.
aliases: type: Array arrayType: type: String minLength: 1
An alternative shorthand version is also available -- wrap the properties within array brackets.
aliases: type: String minLength: 1
unique
Ensures duplicate-free array, using === to test object equality.
emails: type: Array unique: true arrayType: String
filter
Reject any values where filter callback does not return truthy.
emails: type: Array arrayType: Person persongender !== 'f'
Object
objectType
Allows you to define a typed object.
company: type: Object objectType: name: String
An alternative shorthand version is also available -- simply pass an instance of SchemaObject or a schema.
company: name: String
Alias
index (required)
The index key of the property being aliased.
zip: StringpostalCode: type: 'alias' alias: 'zip'// this.postalCode = 12345 -> this.toObject() -> {zip: '12345'}