fume-fhir-converter
Version:
FHIR-Utilized Mapping Engine - Community
231 lines (217 loc) • 8.05 kB
text/typescript
/**
* © Copyright Outburn Ltd. 2022-2024 All Rights Reserved
* Project name: FUME-COMMUNITY
*/
import _ from 'lodash';
import { isEmpty } from '../objectFunctions';
// import thrower from '../thrower';
export interface FlashMergeOptions {
key: string // name of the element
max?: string // maximum cardinality
min?: number // minimum cardinality
baseMax?: string // maximum cardinality from base definition. >1 or * means value is always an array
path?: string // The path to this element of the object
eDefPath?: string // The path of the definition.
kind?: string // the kind of element as defined in the type's StructureDefinition
mandatory?: any // object containing all mandory elements that have fixed children
};
const deleteEntriesWithSamePath = async (obj, path) => {
const tempObj = Object.fromEntries(Object.entries(obj).filter(([key, value]) => value === path || (path.includes('[x]') && String(value).includes(path.slice(0, -3)))));
const empty: boolean = await isEmpty(tempObj);
if (!empty) {
const keyName = _.keys(tempObj)[0].split('__path_')[1];
return Object.fromEntries(Object.entries(obj).filter(([key, value]) => !key.includes(keyName)));
} else {
return obj;
}
};
const sliceByMax = (arr, max) => {
return arr.length ? arr.slice(-1 * Number(max))[0] : null;
};
const addToArr = (arr, value) => {
if (Array.isArray(value)) {
arr = arr.concat(value);
} else {
arr.push(value);
}
return arr;
};
export const flashMerge = async (options: FlashMergeOptions, baseObj: any, ruleObj: any): Promise<any> => {
// const empty: boolean = await isEmpty(ruleObj);
// if (options?.min === 0) {
// if (empty) return baseObj;
// } else {
// let amountOfElements = 1;
// if (empty) {
// amountOfElements = 0;
// } else {
// try {
// amountOfElements = JSON.parse(ruleObj[options.key]).length;
// } catch (e) {}
// }
// // if (amountOfElements < Number(options.min)) {
// // thrower.throwRuntimeError(`Element '${options.path ?? ''}' has a minimum cardinality of ${options.min ?? ''}, got ${amountOfElements} instead`);
// // }
// }
// this function merges key:value into an existing object
// taking into account cardinality and children of primitives
const isBaseArray: boolean = typeof options?.baseMax !== 'undefined' && (options?.baseMax > '1' || options?.baseMax === '*');
const isArray: boolean = typeof options?.max !== 'undefined' && (options?.max > '1' || options?.max === '*');
const key: string = options.key;
const primitive: boolean = options?.kind === 'primitive-type' || !(options?.kind);
const mandatoryObj: any = options?.mandatory;
const newBaseObj = { ...baseObj };
let prevKeyValue;
if (mandatoryObj) {
if (_.get(newBaseObj, key)) {
prevKeyValue = newBaseObj[key];
}
newBaseObj[key] = mandatoryObj;
};
let fullUrl: string;
if (Object.prototype.hasOwnProperty.call(ruleObj, 'fullUrl')) {
// logger.info({ fullUrl: ruleObj.fullUrl, options });
fullUrl = ruleObj.fullUrl;
newBaseObj.fullUrl = fullUrl;
};
if (isArray && !primitive) {
let arr: any[] = [];
if (prevKeyValue) {
arr = addToArr(arr, prevKeyValue);
}
if ((typeof newBaseObj === 'object') && newBaseObj[key] && !mandatoryObj) {
const baseValue = newBaseObj[key];
arr = addToArr(arr, baseValue);
};
if (ruleObj[key]) {
const addValue = ruleObj[key];
let finalObject = addValue;
if (mandatoryObj) {
if (Array.isArray(addValue)) {
finalObject = { ...mandatoryObj };
addValue.forEach(obj => {
const [key, value] = Object.entries(obj)[0];
if (finalObject[key] !== undefined) {
// The key exists in both objects, convert values to an array
if (Array.isArray(value)) {
value.forEach(e => finalObject[key].push(e));
} else {
finalObject[key].push(value);
}
} else {
// The key is unique to the array object, add it to the final object
finalObject[key] = value;
}
});
}
finalObject = { ...mandatoryObj, ...finalObject };
}
arr = addToArr(arr, finalObject);
};
const resObj = { ...newBaseObj };
if (arr.length > 0) resObj[key] = arr;
return resObj;
} else if (isArray && primitive) {
// primitives in fhir have children that are represented in a dedicated object
// when the element is repeating, the array of primitive values and the array of objects must be synced
let arr1: any[] = [];
if ((typeof newBaseObj === 'object') && newBaseObj[key]) {
const baseValue = newBaseObj[key];
arr1 = addToArr(arr1, baseValue);
}
let arr2: any[] = [];
if ((typeof newBaseObj === 'object') && newBaseObj['_' + key]) {
const baseValue = newBaseObj['_' + key];
arr2 = addToArr(arr2, baseValue);
};
if (arr1.length === arr2.length) {
if (ruleObj[key]) {
try {
const parsedVal = JSON.parse(ruleObj[key]);
ruleObj[key] = typeof parsedVal === 'number' ? ruleObj[key] : parsedVal;
} catch (e) {}
if (Array.isArray(ruleObj[key]) && options.max !== '*') {
ruleObj[key] = ruleObj[key].slice(Number(options.max) * -1);
}
const newValue = ruleObj[key];
arr1 = addToArr(arr1, newValue);
};
if (ruleObj['_' + key]) {
const newValue = ruleObj['_' + key];
arr2 = addToArr(arr2, newValue);
};
} else {
// fill in with nulls before push
const diff: number = arr1.length - arr2.length;
const absDiff: number = Math.abs(diff);
const fill: any[] = Array(absDiff).fill(null);
if (diff > 0) {
// positive number, so fill arr2
// but only if we have anything to add to it
if (ruleObj['_' + key]) arr2 = [...arr2, ...fill];
} else {
// negative diff, so fill arr1
// but only if we have anything to add to it
if (ruleObj[key]) arr1 = [...arr1, ...fill];
};
if (ruleObj[key]) { // primitive array
const newValue = ruleObj[key];
arr1 = addToArr(arr1, newValue);
}
if (ruleObj['_' + key]) { // children object array
const newValue = ruleObj['_' + key];
arr2 = addToArr(arr2, newValue);
}
}
const resObj = { ...newBaseObj };
if (arr1.length > 0) resObj[key] = arr1;
if (arr2.length > 0) resObj['_' + key] = arr2;
return resObj;
} else {
// not an array
let resObj = { ...newBaseObj };
resObj = await deleteEntriesWithSamePath(resObj, options.eDefPath);
if (primitive) {
try {
const parsedValue = JSON.parse(ruleObj[key]);
if (typeof parsedValue === 'object') {
// The parsed value is a non-array object
ruleObj[key] = parsedValue;
}
} catch (e) {}
if (Array.isArray(ruleObj[key]) && options.max) {
ruleObj[key] = sliceByMax(ruleObj[key], options.max);
}
resObj[key] = ruleObj[key];
resObj['_' + key] = ruleObj['_' + key];
resObj['__path_' + key] = options?.path;
} else {
if (ruleObj[key]) {
if (Array.isArray(ruleObj[key]) && options.max) {
ruleObj[key] = sliceByMax(ruleObj[key], options.max);
}
const newVal = ruleObj[key];
const baseVal = resObj[key];
const merged = { ...baseVal, ...newVal };
resObj[key] = merged;
}
}
if (isBaseArray) {
if (resObj[key]) {
let arr1: any[] = [];
const val: any[] = resObj[key];
arr1 = addToArr(arr1, val);
resObj[key] = arr1;
}
if (resObj['_' + key]) {
let arr2: any[] = [];
const val: any[] = resObj['_' + key];
arr2 = addToArr(arr2, val);
resObj['_' + key] = arr2;
}
return resObj;
} else {
return resObj;
}
}
};