UNPKG

@smartimpact-it/json-merge-shopify

Version:

The library handles merging JSON objects and arrays.

270 lines (269 loc) 10.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ArrayMerger = void 0; const Comparer_1 = __importDefault(require("./Comparer")); const sourceNames = ['base', 'ours', 'theirs']; class ArrayMerger { constructor({ base, ours, theirs, preferred, filename, path, deletionsAllowed, }) { this.variants = null; this.objectPossiblePositions = null; this.deletionsAllowed = true; this.base = base; this.ours = ours; this.theirs = theirs; this.preferred = preferred || 'theirs'; this.filename = filename || null; this.path = path || []; this.union = this.getUnion(); this.deletionsAllowed = deletionsAllowed ?? true; } getUnion() { let union; if (this.preferred === 'ours') { union = [...new Set([...this.ours, ...this.theirs])]; } else { union = [...new Set([...this.theirs, ...this.ours])]; } return union; } getObjectPositions() { if (this.objectPossiblePositions) { return this.objectPossiblePositions; } const comparer = new Comparer_1.default(); const objectPositions = []; this.union.forEach((object, index) => { const result = comparer.getArrayElementDiff3(object, { base: this.base, theirs: this.theirs, ours: this.ours, }); objectPositions.push({ unionIndex: index, ...result, }); }); this.objectPossiblePositions = objectPositions; return objectPositions; } getPositionsAsArray(position, type, currentVariants) { if (position === null) { return [null]; } let positions; if (Array.isArray(position)) { positions = position; } else { positions = [{ index: position, similarity: 1 }]; } positions = positions.filter((position) => { return !currentVariants.find((variant) => { return variant.positions.find((variantPosition) => { if (position === null) { return false; } switch (type) { case 'base': return position.index === variantPosition.basePosition; case 'ours': return position.index === variantPosition.oursPosition; case 'theirs': return position.index === variantPosition.theirsPosition; } }); }); }); if (positions.length === 0) { return [null]; } return positions; } getPositionIndex(position) { return typeof position === 'object' && position != null ? position.index : position; } getPositionScore(position) { return typeof position === 'object' && position != null ? position.similarity : position !== null ? 1 : null; } getNextVariants(currentVariants, unionIndex) { if (unionIndex === this.union.length) { return currentVariants; } const objectPositions = this.getObjectPositions(); const object = this.union[unionIndex]; const objectPosition = objectPositions.find((objectPosition) => objectPosition.unionIndex === unionIndex); if (!objectPosition) { return currentVariants; } // Create new variants for each possible position of the current object. const newVariants = []; const { basePosition, oursPosition, theirsPosition } = objectPosition; const basePositions = this.getPositionsAsArray(basePosition, 'base', currentVariants); const oursPositions = this.getPositionsAsArray(oursPosition, 'ours', currentVariants); const theirsPositions = this.getPositionsAsArray(theirsPosition, 'theirs', currentVariants); basePositions.forEach((basePosition) => { oursPositions.forEach((oursPosition) => { theirsPositions.forEach((theirsPosition) => { const basePositionIndex = this.getPositionIndex(basePosition); const oursPositionIndex = this.getPositionIndex(oursPosition); const theirsPositionIndex = this.getPositionIndex(theirsPosition); const basePositionScore = this.getPositionScore(basePosition); const oursPositionScore = this.getPositionScore(oursPosition); const theirsPositionScore = this.getPositionScore(theirsPosition); let similarityScore = (basePositionScore || 0) + (oursPositionScore || 0) + (theirsPositionScore || 0); // If the object is in the same position in base and ours or base and theirs, // it is more likely to be valid. if (basePosition != null) { if (basePositionIndex === oursPositionIndex || basePositionIndex === theirsPositionIndex) { similarityScore *= 1.5; } } const variant = { seemsValid: true, positions: [ { unionPosition: unionIndex, basePosition: basePositionIndex, oursPosition: oursPositionIndex, theirsPosition: theirsPositionIndex, }, ], similarityScore: similarityScore, }; newVariants.push(variant); }); }); }); // If there are no variants yet, go to the next object. if (currentVariants.length === 0) { return this.getNextVariants(newVariants, unionIndex + 1); } // Append new variants to the current variants. const nextVariants = []; currentVariants.forEach((currentVariant) => { newVariants.forEach((newVariant) => { const variant = { seemsValid: currentVariant.seemsValid && newVariant.seemsValid, positions: [...currentVariant.positions, ...newVariant.positions], similarityScore: currentVariant.similarityScore + newVariant.similarityScore, }; nextVariants.push(variant); }); }); // Go to the next object. return this.getNextVariants(nextVariants, unionIndex + 1); } getVariants() { if (this.variants) { return this.variants; } this.variants = this.getNextVariants([], 0); return this.variants; } generateFromVariant(variant) { const positions = variant.positions; const result = []; positions.forEach((position) => { // Deleted object. if (position.basePosition !== null && (position.oursPosition === null || (position.theirsPosition === null && this.deletionsAllowed))) { return; } // Added object. if (position.basePosition === null && (position.oursPosition !== null || position.theirsPosition !== null)) { const ourObject = position.oursPosition ? this.ours[position.oursPosition] : null; const theirObject = position.theirsPosition ? this.theirs[position.theirsPosition] : null; result.push(this.getMergedObject(theirObject, ourObject)); return; } // Modified object. let object; if (position.theirsPosition !== null && position.oursPosition !== null) { object = this.getMergedObject(this.theirs[position.theirsPosition], this.ours[position.oursPosition]); } else { object = this.union[position.unionPosition]; } result.push(object); }); // Add objects that are missing from the variant. const sources = this.preferred === 'ours' ? ['ours', 'theirs'] : ['theirs', 'ours']; sources.forEach((source) => { const handledIndexes = positions .map((position) => { return position[`${source}Position`]; }) .filter((position) => position !== null); for (let i = 0; i < this[source].length; i++) { if (!handledIndexes.includes(i)) { result.push(this[source][i]); } } }); return result; } getMergedObject(theirs, ours) { const oursIsObject = typeof ours === 'object' || ours == null; const theirsIsObject = typeof theirs === 'object' || theirs == null; // If one of the objects is not an object, return the other one. if ((!oursIsObject && !theirsIsObject) || oursIsObject != theirsIsObject) { return this.preferred === 'ours' ? ours : theirs; } theirs = theirs || {}; ours = ours || {}; // If both objects are objects, merge them. if (this.preferred === 'ours') { return { ...theirs, ...ours, }; } else { return { ...ours, ...theirs, }; } } merge() { const variants = this.getVariants() .sort((a, b) => { return b.similarityScore - a.similarityScore; }) .map((variant) => { return { ...variant, positions: variant.positions.filter((position) => { return (position.oursPosition !== null || position.theirsPosition !== null); }), }; }); const bestVariant = variants[0]; if (!bestVariant) { throw new Error('No valid variant found.'); } const result = this.generateFromVariant(bestVariant); return result; } } exports.ArrayMerger = ArrayMerger; exports.default = ArrayMerger;