json-merger
Version:
Merge JSON (or YAML) files and objects with indicators like $import $remove $replace $merge etc
308 lines • 12.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.SourceType = void 0;
const path_1 = __importDefault(require("path"));
const jsonpath_1 = __importDefault(require("jsonpath"));
const json_ptr_1 = require("json-ptr");
const types_1 = require("./utils/types");
const Scope_1 = require("./Scope");
class Processor {
constructor(_config, _dataLoader) {
this._config = _config;
this._dataLoader = _dataLoader;
this._cache = [];
this._enabledOperationNames = [];
this._nameOperationMap = {};
this._operationNames = [];
this._enabledOperationNames = this._operationNames;
}
merge(sources) {
const scopeVariables = {
$params: this._config.params,
};
const scope = new Scope_1.GlobalScope();
this._enterScope(scope);
let result = sources.reduce((target, source) => {
if (source.type === 0) {
target = this.mergeObject(source.object, target, scopeVariables);
}
else if (source.type === 1) {
target = this.mergeFile(source.uri, target, scopeVariables);
}
return target;
}, undefined);
if (scope.hasRegisteredPhase("afterMerges")) {
result = this.mergeObject(result, undefined, scopeVariables, "afterMerges");
}
this._leaveScope();
return result;
}
mergeFile(uri, target, scopeVariables) {
return this.loadAndProcessFileByRef(uri, target, scopeVariables, true);
}
mergeObject(source, target, scopeVariables, phase) {
const scope = new Scope_1.RootMergeObjectScope(source, target, this.currentScope, scopeVariables, phase);
this._enterScope(scope);
let result = this.processSource(source, target);
this._leaveScope();
if (scope.hasRegisteredPhase("afterMerge")) {
result = this.mergeObject(result, target, scopeVariables, "afterMerge");
}
return result;
}
addOperation(operation) {
const name = operation.name();
this._nameOperationMap[name] = operation;
this._operationNames.push(name);
}
addOperations(operations) {
operations.forEach((operation) => this.addOperation(operation));
}
enableOperations() {
this._enabledOperationNames = this._operationNames;
}
disableOperations() {
this._enabledOperationNames = [];
}
getKeyword(operationName) {
return this._config.operationPrefix + operationName;
}
isKeyword(input) {
if (!this.startsWithOperationPrefix(input)) {
return false;
}
const name = this.stripOperationPrefix(input);
return this._nameOperationMap[name] !== undefined;
}
isEscapedKeyword(input) {
return this.isKeyword(this.stripOperationPrefix(input));
}
stripOperationPrefix(input) {
return input.substr(this._config.operationPrefix.length);
}
startsWithOperationPrefix(input) {
const prefix = this._config.operationPrefix;
return input.substr(0, prefix.length) === prefix;
}
getCurrentUri() {
const scope = this.currentScope;
if (scope.root && scope.root.sourceFilePath) {
return scope.root.sourceFilePath;
}
else if (this._config.cwd !== "") {
return path_1.default.join(this._config.cwd, "object.json");
}
return path_1.default.join(process.cwd(), "object.json");
}
loadFile(uri) {
return this._dataLoader.load(uri, this.getCurrentUri());
}
loadFileByRef(ref) {
const [uri, pointer] = ref.split("#");
let result = this.loadFile(uri);
if (pointer !== undefined) {
result = this.resolveJsonPointer(result, pointer);
}
return result;
}
loadAndProcessFile(uri, target, scopeVariables, isRoot = false) {
const currentUri = this.getCurrentUri();
const absoluteUri = this._dataLoader.toAbsoluteUri(uri, currentUri);
let usedScopeVariables = scopeVariables;
if (usedScopeVariables === undefined && this.currentScope) {
usedScopeVariables = this.currentScope.localVariables;
}
const hashedScopeVariables = JSON.stringify(usedScopeVariables);
const cacheItem = this._cache.filter((x) => x.absoluteUri === absoluteUri &&
x.target === target &&
x.hashedScopeVariables === hashedScopeVariables)[0];
if (cacheItem) {
if (cacheItem.executeAfterMergesPhase) {
this.currentScope.registerPhase("afterMerges");
}
return cacheItem.result;
}
const source = this._dataLoader.load(absoluteUri, currentUri);
scopeVariables = scopeVariables || {};
if (!scopeVariables.$params) {
scopeVariables.$params = this.currentScope.scopeVariables.$params;
}
let scope;
if (isRoot) {
scope = new Scope_1.RootMergeFileScope(absoluteUri, source, target, this.currentScope, scopeVariables, this.currentScope.phase);
}
else {
scope = new Scope_1.MergeFileScope(absoluteUri, source, target, this.currentScope, scopeVariables, this.currentScope.phase);
}
this._enterScope(scope);
let result = this.processSource(source, target);
if (scope.hasRegisteredPhase("afterMerge")) {
const mergeObjectScope = new Scope_1.MergeObjectScope(result, undefined, scope, scopeVariables, "afterMerge");
this._enterScope(mergeObjectScope);
result = this.processSource(result);
this._leaveScope();
}
this._leaveScope();
const executeAfterMergesPhase = scope.hasRegisteredPhase("afterMerges");
this._cache.push({
absoluteUri,
target,
hashedScopeVariables,
result,
executeAfterMergesPhase,
});
return result;
}
loadAndProcessFileByRef(ref, target, scopeVariables, isRoot = false) {
const [uri, pointer] = ref.split("#");
let result = this.loadAndProcessFile(uri, target, scopeVariables, isRoot);
if (pointer !== undefined) {
result = this.resolveJsonPointer(result, pointer);
}
return result;
}
processSourcePropertyInNewMergeObjectScope(sourceProperty, sourcePropertyName, targetProperty, scopeVariables) {
const scope = new Scope_1.MergeObjectScope(sourceProperty, targetProperty, this.currentScope, scopeVariables);
this._enterScope(scope);
const result = this.processSourceProperty(sourceProperty, sourcePropertyName, targetProperty);
this._leaveScope();
return result;
}
processSourcePropertyInNewScope(sourceProperty, sourcePropertyName, targetProperty, scopeVariables) {
const scope = new Scope_1.Scope(this.currentScope, scopeVariables);
this._enterScope(scope);
const result = this.processSourceProperty(sourceProperty, sourcePropertyName, targetProperty);
this._leaveScope();
return result;
}
processSourceProperty(sourceProperty, sourcePropertyName, targetProperty) {
this.currentScope.enterProperty(sourcePropertyName);
const modifiedSourceProperty = this.addDefaultArrayMergeStrategy(sourceProperty, targetProperty);
const result = this.processSource(modifiedSourceProperty, targetProperty);
this.currentScope.leaveProperty();
return result;
}
addDefaultArrayMergeStrategy(sourceProperty, targetProperty) {
const isBothArray = Array.isArray(sourceProperty) && Array.isArray(targetProperty);
if (isBothArray) {
const keyword = this.getKeyword(this._config.defaultArrayMergeOperation);
return { [keyword]: sourceProperty };
}
return sourceProperty;
}
processSource(source, target) {
if ((0, types_1.isObject)(source)) {
return this._processObject(source, target);
}
else if (Array.isArray(source)) {
return this._processArray(source, target);
}
return source;
}
_processObject(source, target) {
for (let i = 0; i < this._enabledOperationNames.length; i++) {
const name = this._enabledOperationNames[i];
const operation = this._nameOperationMap[name];
const keyword = this.getKeyword(name);
if (source[keyword] !== undefined) {
this.currentScope.enterProperty(keyword);
const result = operation.processInObject(keyword, source, target);
this.currentScope.leaveProperty();
return result;
}
}
if (!(0, types_1.isObject)(target)) {
target = {};
}
const result = Object.assign({}, target);
Object.keys(source).forEach((key) => {
if (this.stripOperationPrefix(key) === "comment") {
return;
}
const targetKey = this.isEscapedKeyword(key)
? this.stripOperationPrefix(key)
: key;
result[targetKey] = this.processSourceProperty(source[key], key, target[key]);
if (typeof result[targetKey] === "undefined") {
delete result[targetKey];
}
});
return result;
}
_processArray(source, target) {
target = (Array.isArray(target) ? target : []);
let processResult = {
resultArray: target.slice(),
resultArrayIndex: -1,
};
source.forEach((sourceItem, sourceItemIndex) => {
this.currentScope.enterProperty(sourceItemIndex);
processResult = this.processArrayItem(sourceItem, source, sourceItemIndex, processResult.resultArray, processResult.resultArrayIndex + 1, target);
this.currentScope.leaveProperty();
});
return processResult.resultArray;
}
processArrayItem(source, sourceArray, sourceArrayIndex, resultArray, resultArrayIndex, target) {
if ((0, types_1.isObject)(source)) {
for (let i = 0; i < this._enabledOperationNames.length; i++) {
const name = this._enabledOperationNames[i];
const operation = this._nameOperationMap[name];
const keyword = this.getKeyword(name);
if (source[keyword] !== undefined) {
this.currentScope.enterProperty(keyword);
const result = operation.processInArray(keyword, source, sourceArray, sourceArrayIndex, resultArray, resultArrayIndex, target);
this.currentScope.leaveProperty();
return result;
}
}
}
resultArray[resultArrayIndex] = this.processSource(source, resultArray[resultArrayIndex]);
return { resultArray, resultArrayIndex };
}
resolveJsonPointer(target, pointer) {
let result;
if (pointer === undefined || pointer === "/") {
result = target;
}
else {
result = json_ptr_1.JsonPointer.get(target, pointer);
}
if (result === undefined && this._config.errorOnRefNotFound) {
throw new Error(`The JSON pointer "${pointer}" resolves to undefined. Set Config.errorOnRefNotFound to false to suppress this message`);
}
return result;
}
resolveJsonPath(target, path) {
let result;
if (path === undefined) {
result = target;
}
else if ((0, types_1.isObject)(target) || Array.isArray(target)) {
result = jsonpath_1.default.query(target, path);
}
if (this._config.errorOnRefNotFound &&
(result === undefined || result.length === 0)) {
throw new Error(`The JSON path "${path}" resolves to undefined. Set Config.errorOnRefNotFound to false to suppress this message`);
}
return result;
}
_enterScope(scope) {
this.currentScope = scope;
return this.currentScope;
}
_leaveScope() {
const currentScope = this.currentScope;
this.currentScope = this.currentScope.parent;
return currentScope;
}
}
exports.default = Processor;
var SourceType;
(function (SourceType) {
SourceType[SourceType["Object"] = 0] = "Object";
SourceType[SourceType["Uri"] = 1] = "Uri";
})(SourceType || (exports.SourceType = SourceType = {}));
//# sourceMappingURL=Processor.js.map