jsonpath-tmf-query
Version:
This library aims to provide a simple wrapper around jsonpath, to ease the implementation of TMF630 JSONPath specification as outlined by the TM Forum [here](https://projects.tmforum.org/wiki/pages/viewpage.action?spaceKey=PUB&title=TMF630+REST+API+Design
187 lines (180 loc) • 6.11 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
const jsonpathPlus = require('jsonpath-plus');
const lodash = require('lodash');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const lodash__default = /*#__PURE__*/_interopDefaultCompat(lodash);
const { get, set } = lodash__default;
const sortOperations = (ops) => ops.sort((opA, opB) => {
const operationOrder = ["sort", "filter", "fields"];
function getOpIndex(operation) {
return operationOrder.indexOf(operation.op);
}
return getOpIndex(opA) - getOpIndex(opB);
});
const removeEmpty = (obj) => {
Object.keys(obj).forEach((key) => {
const value = obj[key];
if (value == null) {
delete obj[key];
} else if (typeof value === "object") {
removeEmpty(value);
if (!Object.keys(value).length) {
delete obj[key];
}
if (Array.isArray(value)) {
for (let i = value.length - 1; i >= 0; i -= 1) {
if (!value[i]) {
value.splice(i, 1);
}
}
}
}
});
};
const checkValidJsonPath = (jsonpathExpression) => {
if (typeof jsonpathExpression !== "string")
return false;
try {
const query = jsonpathPlus.JSONPath.toPathArray(jsonpathExpression);
return !query.some((element) => {
if (element === "") {
throw new Error();
}
});
} catch (_e) {
try {
const query = jsonpathPlus.JSONPath.toPathArray(`$${jsonpathExpression}`);
return !query.some((element) => {
if (element === "") {
return true;
}
});
} catch (_e2) {
return false;
}
}
};
class JSONPathQuery {
static query(document, operations) {
const rootIsArray = Array.isArray(document);
const mutatedDocument = JSON.parse(JSON.stringify(document));
const hasFields = operations.some((operation) => operation.op === "fields");
const hasFilter = operations.some((operation) => operation.op === "filter");
let hasSort = operations.some((operation) => operation.op === "sort");
let newDocument;
let filteredDocument = rootIsArray ? [] : {};
if (hasFilter || rootIsArray)
newDocument = [];
else
newDocument = {};
const pathPrefix = rootIsArray || hasFilter ? "$[*]." : "$.";
if (hasFields) {
operations.push({
op: "fields",
path: `${pathPrefix}id`
});
operations.push({
op: "fields",
path: `${pathPrefix}href`
});
}
const sortedOperations = sortOperations(operations);
sortedOperations.forEach((operation, index) => {
hasSort = operations.slice(index).some((remainingOp) => remainingOp.op === "sort");
let paths;
if (operation.path.startsWith("$") === false && operation.path !== "none") {
if (rootIsArray)
operation.path = `$${operation.path}`;
else
operation.path = `$.${operation.path}`;
}
if (hasSort) {
paths = jsonpathPlus.JSONPath({
path: operation.path,
json: mutatedDocument,
resultType: "path"
});
} else if (hasFilter) {
paths = jsonpathPlus.JSONPath({
path: operation.path,
json: filteredDocument,
resultType: "path"
});
} else {
paths = jsonpathPlus.JSONPath({
path: operation.path,
json: mutatedDocument,
resultType: "path"
});
}
switch (operation.op) {
case "filter":
filteredDocument = jsonpathPlus.JSONPath({
path: operation.path,
json: mutatedDocument
});
if (operation.limit && operation.offset) {
filteredDocument = filteredDocument.slice(operation.offset, operation.offset + operation.limit);
} else if (operation.limit) {
filteredDocument = filteredDocument.slice(0, operation.limit);
} else if (operation.offset) {
filteredDocument = filteredDocument.slice(operation.offset);
}
if (!hasFields)
newDocument = filteredDocument;
break;
case "fields":
if (hasFilter) {
paths.forEach((_path) => {
const path = jsonpathPlus.JSONPath.toPathArray(_path).filter((p) => p !== "$");
const element = get(filteredDocument, path);
set(newDocument, path, element);
});
} else {
paths.forEach((_path) => {
const path = jsonpathPlus.JSONPath.toPathArray(_path).filter((p) => p !== "$");
const element = get(mutatedDocument, path);
set(newDocument, path, element);
});
}
break;
case "sort":
let jsonPathQuery = jsonpathPlus.JSONPath.toPathArray(operation.path);
const sortParam = jsonPathQuery[jsonPathQuery.length - 1];
jsonPathQuery = jsonPathQuery.slice(0, -2);
const stringPath = jsonpathPlus.JSONPath.toPathString(jsonPathQuery);
const arrayOfNodes = jsonpathPlus.JSONPath({
path: stringPath,
json: mutatedDocument
});
arrayOfNodes[0].sort((valueA, valueB) => {
if (operation.order === "asc") {
if (valueA[`${sortParam}`] < valueB[`${sortParam}`]) {
return -1;
}
if (valueA[`${sortParam}`] > valueB[`${sortParam}`]) {
return 1;
}
} else {
if (valueB[`${sortParam}`] < valueA[`${sortParam}`]) {
return -1;
}
if (valueB[`${sortParam}`] > valueA[`${sortParam}`]) {
return 1;
}
}
return 0;
});
break;
}
});
if (hasSort && hasFields === false && hasFilter === false) {
return mutatedDocument;
}
removeEmpty(newDocument);
return newDocument;
}
}
exports.checkValidJsonPath = checkValidJsonPath;
exports.default = JSONPathQuery;