normalizr
Version:
Normalizes and denormalizes JSON according to schema for Redux and Flux applications
604 lines (465 loc) • 18.7 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.normalizr = {})));
}(this, (function (exports) { 'use strict';
/**
* Helpers to enable Immutable compatibility *without* bringing in
* the 'immutable' package as a dependency.
*/
/**
* Check if an object is immutable by checking if it has a key specific
* to the immutable library.
*
* @param {any} object
* @return {bool}
*/
function isImmutable(object) {
return !!(object && typeof object.hasOwnProperty === 'function' && (object.hasOwnProperty('__ownerID') || // Immutable.Map
object._map && object._map.hasOwnProperty('__ownerID'))); // Immutable.Record
}
/**
* Denormalize an immutable entity.
*
* @param {Schema} schema
* @param {Immutable.Map|Immutable.Record} input
* @param {function} unvisit
* @param {function} getDenormalizedEntity
* @return {Immutable.Map|Immutable.Record}
*/
function denormalizeImmutable(schema, input, unvisit) {
return Object.keys(schema).reduce(function (object, key) {
// Immutable maps cast keys to strings on write so we need to ensure
// we're accessing them using string keys.
var stringKey = '' + key;
if (object.has(stringKey)) {
return object.set(stringKey, unvisit(object.get(stringKey), schema[stringKey]));
} else {
return object;
}
}, input);
}
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var getDefaultGetId = function getDefaultGetId(idAttribute) {
return function (input) {
return isImmutable(input) ? input.get(idAttribute) : input[idAttribute];
};
};
var EntitySchema = function () {
function EntitySchema(key) {
var definition = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
classCallCheck(this, EntitySchema);
if (!key || typeof key !== 'string') {
throw new Error('Expected a string key for Entity, but found ' + key + '.');
}
var _options$idAttribute = options.idAttribute,
idAttribute = _options$idAttribute === undefined ? 'id' : _options$idAttribute,
_options$mergeStrateg = options.mergeStrategy,
mergeStrategy = _options$mergeStrateg === undefined ? function (entityA, entityB) {
return _extends({}, entityA, entityB);
} : _options$mergeStrateg,
_options$processStrat = options.processStrategy,
processStrategy = _options$processStrat === undefined ? function (input) {
return _extends({}, input);
} : _options$processStrat;
this._key = key;
this._getId = typeof idAttribute === 'function' ? idAttribute : getDefaultGetId(idAttribute);
this._idAttribute = idAttribute;
this._mergeStrategy = mergeStrategy;
this._processStrategy = processStrategy;
this.define(definition);
}
EntitySchema.prototype.define = function define(definition) {
this.schema = Object.keys(definition).reduce(function (entitySchema, key) {
var _babelHelpers$extends;
var schema = definition[key];
return _extends({}, entitySchema, (_babelHelpers$extends = {}, _babelHelpers$extends[key] = schema, _babelHelpers$extends));
}, this.schema || {});
};
EntitySchema.prototype.getId = function getId(input, parent, key) {
return this._getId(input, parent, key);
};
EntitySchema.prototype.merge = function merge(entityA, entityB) {
return this._mergeStrategy(entityA, entityB);
};
EntitySchema.prototype.normalize = function normalize(input, parent, key, visit, addEntity) {
var _this = this;
var processedEntity = this._processStrategy(input, parent, key);
Object.keys(this.schema).forEach(function (key) {
if (processedEntity.hasOwnProperty(key) && _typeof(processedEntity[key]) === 'object') {
var schema = _this.schema[key];
processedEntity[key] = visit(processedEntity[key], processedEntity, key, schema, addEntity);
}
});
addEntity(this, processedEntity, input, parent, key);
return this.getId(input, parent, key);
};
EntitySchema.prototype.denormalize = function denormalize(entity, unvisit) {
var _this2 = this;
if (isImmutable(entity)) {
return denormalizeImmutable(this.schema, entity, unvisit);
}
Object.keys(this.schema).forEach(function (key) {
if (entity.hasOwnProperty(key)) {
var schema = _this2.schema[key];
entity[key] = unvisit(entity[key], schema);
}
});
return entity;
};
createClass(EntitySchema, [{
key: 'key',
get: function get$$1() {
return this._key;
}
}, {
key: 'idAttribute',
get: function get$$1() {
return this._idAttribute;
}
}]);
return EntitySchema;
}();
var PolymorphicSchema = function () {
function PolymorphicSchema(definition, schemaAttribute) {
classCallCheck(this, PolymorphicSchema);
if (schemaAttribute) {
this._schemaAttribute = typeof schemaAttribute === 'string' ? function (input) {
return input[schemaAttribute];
} : schemaAttribute;
}
this.define(definition);
}
PolymorphicSchema.prototype.define = function define(definition) {
this.schema = definition;
};
PolymorphicSchema.prototype.getSchemaAttribute = function getSchemaAttribute(input, parent, key) {
return !this.isSingleSchema && this._schemaAttribute(input, parent, key);
};
PolymorphicSchema.prototype.inferSchema = function inferSchema(input, parent, key) {
if (this.isSingleSchema) {
return this.schema;
}
var attr = this.getSchemaAttribute(input, parent, key);
return this.schema[attr];
};
PolymorphicSchema.prototype.normalizeValue = function normalizeValue(value, parent, key, visit, addEntity) {
var schema = this.inferSchema(value, parent, key);
if (!schema) {
return value;
}
var normalizedValue = visit(value, parent, key, schema, addEntity);
return this.isSingleSchema || normalizedValue === undefined || normalizedValue === null ? normalizedValue : { id: normalizedValue, schema: this.getSchemaAttribute(value, parent, key) };
};
PolymorphicSchema.prototype.denormalizeValue = function denormalizeValue(value, unvisit) {
var schemaKey = isImmutable(value) ? value.get('schema') : value.schema;
if (!this.isSingleSchema && !schemaKey) {
return value;
}
var id = isImmutable(value) ? value.get('id') : value.id;
var schema = this.isSingleSchema ? this.schema : this.schema[schemaKey];
return unvisit(id || value, schema);
};
createClass(PolymorphicSchema, [{
key: 'isSingleSchema',
get: function get$$1() {
return !this._schemaAttribute;
}
}]);
return PolymorphicSchema;
}();
var UnionSchema = function (_PolymorphicSchema) {
inherits(UnionSchema, _PolymorphicSchema);
function UnionSchema(definition, schemaAttribute) {
classCallCheck(this, UnionSchema);
if (!schemaAttribute) {
throw new Error('Expected option "schemaAttribute" not found on UnionSchema.');
}
return possibleConstructorReturn(this, _PolymorphicSchema.call(this, definition, schemaAttribute));
}
UnionSchema.prototype.normalize = function normalize(input, parent, key, visit, addEntity) {
return this.normalizeValue(input, parent, key, visit, addEntity);
};
UnionSchema.prototype.denormalize = function denormalize(input, unvisit) {
return this.denormalizeValue(input, unvisit);
};
return UnionSchema;
}(PolymorphicSchema);
var ValuesSchema = function (_PolymorphicSchema) {
inherits(ValuesSchema, _PolymorphicSchema);
function ValuesSchema() {
classCallCheck(this, ValuesSchema);
return possibleConstructorReturn(this, _PolymorphicSchema.apply(this, arguments));
}
ValuesSchema.prototype.normalize = function normalize(input, parent, key, visit, addEntity) {
var _this2 = this;
return Object.keys(input).reduce(function (output, key, index) {
var _babelHelpers$extends;
var value = input[key];
return value !== undefined && value !== null ? _extends({}, output, (_babelHelpers$extends = {}, _babelHelpers$extends[key] = _this2.normalizeValue(value, input, key, visit, addEntity), _babelHelpers$extends)) : output;
}, {});
};
ValuesSchema.prototype.denormalize = function denormalize(input, unvisit) {
var _this3 = this;
return Object.keys(input).reduce(function (output, key) {
var _babelHelpers$extends2;
var entityOrId = input[key];
return _extends({}, output, (_babelHelpers$extends2 = {}, _babelHelpers$extends2[key] = _this3.denormalizeValue(entityOrId, unvisit), _babelHelpers$extends2));
}, {});
};
return ValuesSchema;
}(PolymorphicSchema);
var validateSchema = function validateSchema(definition) {
var isArray = Array.isArray(definition);
if (isArray && definition.length > 1) {
throw new Error('Expected schema definition to be a single schema, but found ' + definition.length + '.');
}
return definition[0];
};
var getValues = function getValues(input) {
return Array.isArray(input) ? input : Object.keys(input).map(function (key) {
return input[key];
});
};
var normalize = function normalize(schema, input, parent, key, visit, addEntity) {
schema = validateSchema(schema);
var values = getValues(input);
// Special case: Arrays pass *their* parent on to their children, since there
// is not any special information that can be gathered from themselves directly
return values.map(function (value, index) {
return visit(value, parent, key, schema, addEntity);
});
};
var denormalize = function denormalize(schema, input, unvisit) {
schema = validateSchema(schema);
return input && input.map ? input.map(function (entityOrId) {
return unvisit(entityOrId, schema);
}) : input;
};
var ArraySchema = function (_PolymorphicSchema) {
inherits(ArraySchema, _PolymorphicSchema);
function ArraySchema() {
classCallCheck(this, ArraySchema);
return possibleConstructorReturn(this, _PolymorphicSchema.apply(this, arguments));
}
ArraySchema.prototype.normalize = function normalize(input, parent, key, visit, addEntity) {
var _this2 = this;
var values = getValues(input);
return values.map(function (value, index) {
return _this2.normalizeValue(value, parent, key, visit, addEntity);
}).filter(function (value) {
return value !== undefined && value !== null;
});
};
ArraySchema.prototype.denormalize = function denormalize(input, unvisit) {
var _this3 = this;
return input && input.map ? input.map(function (value) {
return _this3.denormalizeValue(value, unvisit);
}) : input;
};
return ArraySchema;
}(PolymorphicSchema);
var _normalize = function _normalize(schema, input, parent, key, visit, addEntity) {
var object = _extends({}, input);
Object.keys(schema).forEach(function (key) {
var localSchema = schema[key];
var value = visit(input[key], input, key, localSchema, addEntity);
if (value === undefined || value === null) {
delete object[key];
} else {
object[key] = value;
}
});
return object;
};
var _denormalize = function _denormalize(schema, input, unvisit) {
if (isImmutable(input)) {
return denormalizeImmutable(schema, input, unvisit);
}
var object = _extends({}, input);
Object.keys(schema).forEach(function (key) {
if (object[key]) {
object[key] = unvisit(object[key], schema[key]);
}
});
return object;
};
var ObjectSchema = function () {
function ObjectSchema(definition) {
classCallCheck(this, ObjectSchema);
this.define(definition);
}
ObjectSchema.prototype.define = function define(definition) {
this.schema = Object.keys(definition).reduce(function (entitySchema, key) {
var _babelHelpers$extends;
var schema = definition[key];
return _extends({}, entitySchema, (_babelHelpers$extends = {}, _babelHelpers$extends[key] = schema, _babelHelpers$extends));
}, this.schema || {});
};
ObjectSchema.prototype.normalize = function normalize() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _normalize.apply(undefined, [this.schema].concat(args));
};
ObjectSchema.prototype.denormalize = function denormalize() {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _denormalize.apply(undefined, [this.schema].concat(args));
};
return ObjectSchema;
}();
var visit = function visit(value, parent, key, schema, addEntity) {
if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) !== 'object' || !value) {
return value;
}
if ((typeof schema === 'undefined' ? 'undefined' : _typeof(schema)) === 'object' && (!schema.normalize || typeof schema.normalize !== 'function')) {
var method = Array.isArray(schema) ? normalize : _normalize;
return method(schema, value, parent, key, visit, addEntity);
}
return schema.normalize(value, parent, key, visit, addEntity);
};
var addEntities = function addEntities(entities) {
return function (schema, processedEntity, value, parent, key) {
var schemaKey = schema.key;
var id = schema.getId(value, parent, key);
if (!(schemaKey in entities)) {
entities[schemaKey] = {};
}
var existingEntity = entities[schemaKey][id];
if (existingEntity) {
entities[schemaKey][id] = schema.merge(existingEntity, processedEntity);
} else {
entities[schemaKey][id] = processedEntity;
}
};
};
var schema = {
Array: ArraySchema,
Entity: EntitySchema,
Object: ObjectSchema,
Union: UnionSchema,
Values: ValuesSchema
};
var normalize$1 = function normalize$$1(input, schema) {
if (!input || (typeof input === 'undefined' ? 'undefined' : _typeof(input)) !== 'object') {
throw new Error('Unexpected input given to normalize. Expected type to be "object", found "' + (typeof input === 'undefined' ? 'undefined' : _typeof(input)) + '".');
}
var entities = {};
var addEntity = addEntities(entities);
var result = visit(input, input, null, schema, addEntity);
return { entities: entities, result: result };
};
var unvisitEntity = function unvisitEntity(id, schema, unvisit, getEntity, cache) {
var entity = getEntity(id, schema);
if ((typeof entity === 'undefined' ? 'undefined' : _typeof(entity)) !== 'object' || entity === null) {
return entity;
}
if (!cache[schema.key]) {
cache[schema.key] = {};
}
if (!cache[schema.key][id]) {
// Ensure we don't mutate it non-immutable objects
var entityCopy = isImmutable(entity) ? entity : _extends({}, entity);
// Need to set this first so that if it is referenced further within the
// denormalization the reference will already exist.
cache[schema.key][id] = entityCopy;
cache[schema.key][id] = schema.denormalize(entityCopy, unvisit);
}
return cache[schema.key][id];
};
var getUnvisit = function getUnvisit(entities) {
var cache = {};
var getEntity = getEntities(entities);
return function unvisit(input, schema) {
if ((typeof schema === 'undefined' ? 'undefined' : _typeof(schema)) === 'object' && (!schema.denormalize || typeof schema.denormalize !== 'function')) {
var method = Array.isArray(schema) ? denormalize : _denormalize;
return method(schema, input, unvisit);
}
if (input === undefined || input === null) {
return input;
}
if (schema instanceof EntitySchema) {
return unvisitEntity(input, schema, unvisit, getEntity, cache);
}
return schema.denormalize(input, unvisit);
};
};
var getEntities = function getEntities(entities) {
var isImmutable$$1 = isImmutable(entities);
return function (entityOrId, schema) {
var schemaKey = schema.key;
if ((typeof entityOrId === 'undefined' ? 'undefined' : _typeof(entityOrId)) === 'object') {
return entityOrId;
}
return isImmutable$$1 ? entities.getIn([schemaKey, entityOrId.toString()]) : entities[schemaKey][entityOrId];
};
};
var denormalize$1 = function denormalize$$1(input, schema, entities) {
if (typeof input !== 'undefined') {
return getUnvisit(entities)(input, schema);
}
};
exports.schema = schema;
exports.normalize = normalize$1;
exports.denormalize = denormalize$1;
Object.defineProperty(exports, '__esModule', { value: true });
})));