UNPKG

normalizr

Version:

Normalizes and denormalizes JSON according to schema for Redux and Flux applications

604 lines (465 loc) 18.7 kB
(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 }); })));