@finnair/path
Version:
Simple object path as array of strings and numbers
195 lines (194 loc) • 6.07 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Path = void 0;
const identifierPattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
class Path {
static ROOT = new Path([]);
path;
constructor(path) {
this.path = path;
Object.freeze(this.path);
Object.freeze(this);
}
index(index) {
Path.validateIndex(index);
return new Path(this.path.concat(index));
}
property(property) {
Path.validateProperty(property);
return new Path(this.path.concat(property));
}
child(key) {
if (typeof key === 'number') {
return this.index(key);
}
return this.property(key);
}
connectTo(newRootPath) {
return new Path(newRootPath.path.concat(this.path));
}
concat(childPath) {
return new Path(this.path.concat(childPath.path));
}
parent() {
switch (this.path.length) {
case 0: return undefined;
case 1: return Path.ROOT;
default: return new Path(this.path.slice(0, -1));
}
}
toJSON() {
return this.path.reduce((pathString, component) => pathString + Path.componentToString(component), '$');
}
equals(other) {
if (other instanceof Path) {
const otherLength = other.length;
if (otherLength === this.length) {
for (let i = 0; i < otherLength; i++) {
if (other.componentAt(i) != this.componentAt(i)) {
return false;
}
}
return true;
}
}
return false;
}
get length() {
return this.path.length;
}
componentAt(index) {
return this.path[index];
}
[Symbol.iterator]() {
return this.path[Symbol.iterator]();
}
get(root) {
if (this.path.length === 0) {
return root;
}
let current = root;
let index = 0;
for (; index < this.path.length - 1 && typeof current === 'object'; index++) {
const component = this.path[index];
current = current[component];
}
if (index === this.path.length - 1 && typeof current === 'object') {
return current[this.path[this.path.length - 1]];
}
return undefined;
}
unset(root) {
return this.set(root, undefined);
}
set(root, value) {
if (this.path.length === 0) {
return value;
}
let pathIndex = -1;
const _root = toObject(root, this.path);
let current = _root;
for (pathIndex = 0; pathIndex < this.path.length - 1 && current; pathIndex++) {
const component = this.path[pathIndex];
const child = toObject(current[component], this.path);
if (child !== undefined) {
current[component] = child;
current = child;
}
}
if (value === undefined) {
if (current !== undefined) {
delete current[this.path[pathIndex]];
// Truncate undefined tail of an array
if (Array.isArray(current)) {
let i = current.length - 1;
while (i >= 0 && current[i] === undefined) {
i--;
}
current.length = i + 1;
}
}
}
else {
current[this.path[pathIndex]] = value;
}
return _root;
function toObject(current, path) {
if (typeof current === 'object') {
return current;
}
else if (value !== undefined) {
if (typeof path[pathIndex + 1] === 'number') {
return [];
}
else {
return {};
}
}
else {
return undefined;
}
}
}
static property(property) {
return Path.ROOT.property(property);
}
static index(index) {
return Path.ROOT.index(index);
}
static of(...path) {
if (path.length === 0) {
return Path.ROOT;
}
path.forEach(this.validateComponent);
return new Path(path);
}
static validateComponent(component) {
const type = typeof component;
if (type === 'number') {
if (component < 0 || !Number.isInteger(component)) {
throw new Error('Expected component to be an integer >= 0');
}
}
else if (type !== 'string') {
throw new Error(`Expected component to be a string or an integer, got ${type}: ${component}`);
}
}
static validateIndex(index) {
if (typeof index !== 'number') {
throw new Error(`Expected index to be a number, got ${index}`);
}
if (index < 0 || !Number.isInteger(index)) {
throw new Error('Expected index to be an integer >= 0');
}
}
static validateProperty(property) {
if (typeof property !== 'string') {
throw new Error(`Expected property to be a string, got ${property}`);
}
}
static isValidIdentifier(str) {
return identifierPattern.test(str);
}
static componentToString(component) {
if (typeof component === 'number') {
return Path.indexToString(component);
}
else {
return Path.propertyToString(component);
}
}
static indexToString(index) {
return '[' + index + ']';
}
static propertyToString(property) {
if (Path.isValidIdentifier(property)) {
return '.' + property;
}
else {
// JsonPath uses single quotes, but that would require custom encoding of single quotes as JSON string encoding doesn't have escape for it
return '[' + JSON.stringify(property) + ']';
}
}
}
exports.Path = Path;