@smartimpact-it/json-merge-shopify
Version:
The library handles merging JSON objects and arrays.
270 lines (269 loc) • 10.8 kB
JavaScript
"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;