Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (function(){
- // do not move this outside of the scope.
- "use strict";
- // default options
- var defaultOptions = {
- // if true, allow null values
- allowNull: false,
- // if true, attempt to convert non-matching types implicitly
- implicitConversion: true,
- // if true, apply property access strictness via a proxy
- proxy: true
- };
- // default values
- var defaultValues = {
- Number: 0,
- String: ""
- };
- // overwrite target fields with source fields that exist
- function apply(target, source) {
- if (target && source) {
- for (var i in source) {
- target[i] = source[i];
- }
- }
- return target;
- }
- // create constructor
- function construct(options) {
- // determine what was passed to the constructor
- options = parseJson(options);
- if (!options.schema) {
- options = { schema: options };
- }
- // need a schema to continue
- if (!options.schema) {
- throw "No schema specified.";
- }
- // apply default options
- options = apply(apply({}, defaultOptions), options);
- // build proxy handler
- var proxyHandler = constructProxyHandler(options);
- // create base constructor
- return function(data) {
- var result = {};
- // assume incoming strings are raw JSON
- data = apply({}, parseJson(data));
- // create properties
- for (var propertyName in options.schema) {
- var propertyType = options.schema[propertyName].name;
- Object.defineProperty(
- result,
- propertyName,
- constructAccessors(propertyName, options)
- );
- // assign defaults if nulls are not allowed
- if (!options.allowNull) {
- if (!(propertyName in data) && exists(defaultValues[propertyType])) {
- result[propertyName] = defaultValues[propertyType];
- }
- }
- }
- // assign initial values
- for (var presetName in data) {
- result[presetName] = data[presetName];
- }
- // make the properties visible to JSON.stringify
- result.toJSON = constructJsonHandler(options);
- // attach a proxy, if the feature is available
- if (typeof Proxy == "function") {
- return new Proxy(result, proxyHandler);
- } else {
- return result;
- }
- };
- }
- // create getter+setter pair
- function constructAccessors(name, options) {
- var value;
- return {
- get: function() { return value; },
- set: constructSetter(
- name,
- options,
- function(newValue) { value = newValue; }
- )
- }
- }
- // create toJSON handler
- function constructJsonHandler(options) {
- return function() {
- var result = {};
- for (var field in options.schema) {
- result[field] = this[field];
- }
- return result;
- };
- }
- // create a proxy handler for object constructors (ES6)
- function constructProxyHandler(options) {
- return {
- // prevent deletion of properties in the schema
- deleteProperty: function(target, property) {
- if (!(property in options.schema)) {
- return delete target[property];
- }
- throw "Can't delete property '" + property + "': it is enforced in the schema.";
- },
- };
- }
- // create setter
- function constructSetter(name, options, setFunc) {
- // cache these values for performance
- var allowNull = options.allowNull;
- var fieldType = options.schema[name];
- var implicit = options.implicitConversion;
- // build the setter
- return function(value) {
- if (!exists(value)) {
- value = null;
- }
- if (value !== null || value === null && allowNull) {
- if (value !== null) {
- if (implicit && value.constructor !== fieldType) {
- value = convert(value, fieldType);
- }
- if (value.constructor === fieldType) {
- setFunc(value);
- } else {
- throw "Can't store " + value.constructor.name + " in '" + name + "'.";
- }
- }
- } else {
- throw "Can't store null in '" + name + "'.";
- }
- };
- }
- // used for implicit conversion
- function convert(value, newType) {
- // null and undefined convert specially
- if (value === null || !exists(value)) {
- return null;
- }
- var oldType = value.constructor;
- // we try to use .toString() if provided
- if (newType === String) {
- if (typeof value.toString == "function") {
- return value.toString();
- } else {
- return String(value);
- }
- }
- // NaN is considered a failed conversion
- if (newType === Number) {
- var converted = Number(value);
- if (!isNaN(converted)) {
- return converted;
- }
- }
- // if all other conversions don't work, fail
- throw "Can't convert " + oldType.toString() + " implicitly.";
- }
- // returns true when an object isn't undefined
- function exists(obj) {
- return (typeof obj != "undefined");
- }
- // parse an object out of a number of input types
- function parseJson(json) {
- if (json === null || !exists(json)) {
- return {};
- } else if (typeof json == "string") {
- return JSON.parse(json);
- } else {
- return json;
- }
- }
- // stick it on the global context
- if (exists(window)) {
- window.Struct = construct;
- } else if (exists(global)) {
- global.Struct = construct;
- } else {
- console.warn("Couldn't really find a context to install Struct into.");
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement