@storable/model
Version:
Base class providing typed properties, validation and serialization
246 lines (195 loc) • 4.77 kB
JavaScript
exports.__esModule = true;
exports.serialize = serialize;
exports.deserialize = deserialize;
exports.createValue = createValue;
exports.serializeValue = serializeValue;
exports.normalizeValue = normalizeValue;
var _isPlainObject = _interopRequireDefault(require("lodash/isPlainObject"));
var _omit = _interopRequireDefault(require("lodash/omit"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function serialize(value, {
filter
} = {}) {
let result = serializeValue(value, {
filter
});
if (result !== value) {
// serializeValue() did the job
return result;
}
if (typeof value !== 'object') {
// The value is a boolean, a number, or a string
return value;
}
if (Array.isArray(value)) {
// The value is an array
return value.map(item => serialize(item, {
filter
}));
} // The value is a plain object
result = {};
for (const [key, val] of Object.entries(value)) {
result[key] = serialize(val, {
filter
});
}
return result;
}
function deserialize(value, {
expectedType,
registry
} = {}) {
let result = createValue(value, {
expectedType,
registry,
isDeserializing: true
});
if (result !== value) {
// createValue() did the job
return result;
}
if (typeof value !== 'object') {
// The value is a boolean, a number, or a string
return value;
}
if (Array.isArray(value)) {
// The value is an array
return value.map(item => deserialize(item, {
expectedType,
registry
}));
} // The value is a plain object
result = {};
for (const [key, val] of Object.entries(value)) {
result[key] = deserialize(val, {
expectedType,
registry
});
}
return result;
}
function createValue(value, {
expectedType,
registry,
fieldName,
isDeserializing
} = {}) {
value = normalizeValue(value, {
fieldName
});
if (value === undefined) {
return undefined;
}
const type = typeof value === 'object' ? value._type : undefined;
if (type) {
value = (0, _omit.default)(value, '_type');
const builtInType = builtInTypes[type];
if (builtInType) {
value = value._value;
if (builtInType.deserialize) {
value = builtInType.deserialize(value);
}
} else {
const Model = getModel(registry, type);
value = new Model(value, {
isDeserializing
});
}
}
if (!expectedType) {
return value;
}
const builtInType = builtInTypes[expectedType];
if (builtInType) {
if (!builtInType.checkType(value)) {
throw new Error(`Type mismatch (field: '${fieldName}', expected: '${expectedType}', provided: '${typeof value}')`);
}
return value;
}
if (value.isOfType && value.isOfType('Model')) {
if (!value.isOfType(expectedType)) {
throw new Error(`Type mismatch (field: '${fieldName}', expected: '${expectedType}', provided: '${value.constructor.getName()}')`);
}
return value;
}
if (typeof value !== 'object') {
throw new Error(`Type mismatch (field: '${fieldName}', expected: 'object', provided: '${typeof value}')`);
}
const Model = getModel(registry, expectedType);
value = new Model(value, {
isDeserializing
});
return value;
}
function serializeValue(value, {
filter,
_level
} = {}) {
if (value === undefined) {
return {
_type: 'undefined'
};
}
if (value instanceof Date) {
return {
_type: 'Date',
_value: value.toISOString()
};
}
if (value.isOfType && value.isOfType('Model')) {
return value.serialize({
filter,
_level: _level + 1
});
}
return value;
}
function normalizeValue(value, {
fieldName
}) {
if (value === null) {
throw new Error(`The 'null' value is not allowed (field: '${fieldName}')`);
}
if (value === undefined || typeof value === 'object' && value._type === 'undefined') {
return undefined;
}
return value;
}
const builtInTypes = {
boolean: {
checkType(value) {
return typeof value === 'boolean';
}
},
number: {
checkType(value) {
return typeof value === 'number';
}
},
string: {
checkType(value) {
return typeof value === 'string';
}
},
object: {
checkType(value) {
return (0, _isPlainObject.default)(value);
}
},
Date: {
checkType(value) {
return value instanceof Date;
},
deserialize(value) {
return new Date(value);
}
}
};
function getModel(registry, name) {
const Model = registry === null || registry === void 0 ? void 0 : registry[name];
if (Model === undefined) {
throw new Error(`Model not found (name: '${name}')`);
}
return Model;
}
;