entodicton
Version:
141 lines (129 loc) • 4.57 kB
JavaScript
const debug = require('./debug')
const helpers = require('./helpers')
function areFirstNEqual(arr1, arr2, n) {
if (n <= 0) return true;
if (arr1.length < n || arr2.length < n) return false;
for (let i = 0; i < n; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
function project(source, filters, path=[]) {
if (['boolean', 'string', 'number'].includes(typeof source)) {
return source
}
if (Array.isArray(source)) {
const result = []
for (const value of source) {
result.push(project(value, filters, [...path, '*']))
}
return result
}
function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
if (!isPlainObject(source) && !Array.isArray(source)) {
return source
}
if (Object.keys(source).length === 0 && filters.length === 0) {
return {};
}
// Find the applicable filter for the current context
const selectedFilters = filters.filter(f => f.match({ context: source, path, pathEquals: helpers.pathEquals }));
if (filters.length == 0) {
if (Array.isArray(source)) {
return source.map((element) => project(element, filters, [...path, '*']))
}
return {};
}
// Get the properties to include from the apply function
let properties = []
for (const filter of selectedFilters) {
for (const property of filter.apply(source)) {
properties.push(property)
}
}
if (source?.checks) {
for (const check of source.checks) {
properties.push(check)
}
}
// update
const updatedProperties = []
for (const property of properties) {
// property that contains a list of properties to be checked
if (property.properties) {
for (const moreProperty of source[property.properties] || []) {
updatedProperties.push(moreProperty)
}
} else {
updatedProperties.push(property)
}
}
properties = updatedProperties
// Build the result object
const result = {};
properties.forEach(prop => {
if (prop.path && (prop.path.length === path.length + 1) && areFirstNEqual(path, prop.path, path.length) && source.hasOwnProperty(prop.path[path.length])) {
const endProp = prop.path[path.length]
if (['boolean', 'string', 'number'].includes(typeof source[endProp])) {
result[endProp] = source[endProp]
} else if (Array.isArray(source[endProp])) {
result[endProp] = []
for (const key of source[endProp].keys()) {
result[endProp].push(project(source[endProp][key], filters, [...path, endProp, key]))
}
} else {
result[endProp] = {}
for (const key in source[endProp]) {
result[endProp][key] = project(source[endProp][key], filters, [...path, endProp, key])
}
}
} else if (source.hasOwnProperty(prop)) {
// If the property is an object and not null, recursively project it
if (typeof source[prop] === 'object' && source[prop] !== null) {
result[prop] = project(source[prop], filters, [...path, prop]);
} else {
// Copy primitive or null properties directly
result[prop] = source[prop];
}
} else if (prop.property && source.hasOwnProperty(prop.property)) {
// If the property is an object and not null, recursively project it
if (typeof source[prop.property] === 'object' && source[prop.property] !== null) {
result[prop.property] = {}
const instantiatedCheck = []
for (const check of prop.check) {
if (typeof check.property == 'function') {
for (const sourceKey of source[prop.property].keys()) {
if (check.property(sourceKey)) {
instantiatedCheck.push({ property: sourceKey, check: check.check })
}
}
} else {
instantiatedCheck.push(check)
}
}
for (const key of instantiatedCheck) {
if (typeof key == 'string') {
result[prop.property][key] = project(source[prop.property][key], filters, [...path, prop.property, key]);
} else {
const f = {
match: () => true,
apply: () => key.check
}
result[prop.property][key.property] = project(source[prop.property][key.property], [...filters, f], [...path, prop.property, key.property]);
}
}
} else {
// Copy primitive or null properties directly
result[prop] = source[prop];
}
}
});
return result;
}
module.exports = {
project
}