'use strict';
/**
* @author schukai GmbH
*/
import {Monster} from '../namespace.js';
import {isObject, isArray, isInteger} from '../types/is.js';
import {validateString, validateInteger} from '../types/validate.js';
import {Base} from '../types/base.js';
import {Stack} from "../types/stack.js";
/**
* path separator
*
* @private
* @type {string}
*/
const DELIMITER = '.';
/**
* Pathfinder class
*
* you can call the method via the monster namespace `new Monster.Data.Pathfinder()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.5.0/dist/modules/data/pathfinder.js';
* console.log(new Monster.Data.Pathfinder())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.5.0/dist/modules/data/pathfinder.js';
* console.log(new Pathfinder())
* </script>
* ```
*
* With the help of the pathfinder, values can be read and written from an object construct.
*
* ```
* new Pathfinder({
* a: {
* b: {
* f: [
* {
* g: false,
* }
* ],
* }
* }
* }).getVia("a.b.f.0.g"); // ↦ false
* ```
*
* if a value is not present or has the wrong type, a corresponding exception is thrown.
*
* ```
* new Pathfinder({}).getVia("a.b.f.0.g"); // ↦ Error
* ```
*
* The `Pathfinder.exists()` method can be used to check whether access to the path is possible.
*
* ```
* new Pathfinder({}).exists("a.b.f.0.g"); // ↦ false
* ```
*
* pathfinder can also be used to build object structures. to do this, the `Pathfinder.setVia()` method must be used.
*
* ```
* obj = {};
* new Pathfinder(obj).setVia('a.b.0.c', true); // ↦ {a:{b:[{c:true}]}}
* ```
*
* @since 1.4.0
* @copyright schukai GmbH
* @memberOf Monster/Data
*/
class Pathfinder extends Base {
/**
* @param {array|object|Map|Set} value
* @since 1.4.0
**/
constructor(object) {
super();
this.object = object;
}
/**
*
* @param {string} path
* @since 1.4.0
* @returns {*}
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
*/
getVia(path) {
validateString(path);
return getValueViaPath(this.object, path);
}
/**
*
* @param {string} path
* @param {*} value
* @returns {Pathfinder}
* @since 1.4.0
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
*/
setVia(path, value) {
validateString(path);
setValueViaPath(this.object, path, value);
return this;
}
/**
*
* @param {string} path
* @return {bool}
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @since 1.4.0
*/
exists(path) {
validateString(path);
try {
getValueViaPath(this.object, path, true);
return true;
} catch (e) {
}
return false;
}
}
Monster.assignToNamespace('Monster.Data', Pathfinder);
export {Monster, Pathfinder}
/**
*
* @param {*} object
* @param [string} path
* @param [boolean} check
* @returns {*}
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
*/
function getValueViaPath(object, path, check) {
if (path === "") {
return object;
}
let parts = path.split(DELIMITER)
let current = parts.shift();
if (isObject(object) || isArray(object)) {
let anchor;
if (object instanceof Map) {
anchor = object.get(current);
} else if (object instanceof Set) {
current = parseInt(current);
validateInteger(current)
anchor = [...object]?.[current];
} else if (isArray(object)) {
current = parseInt(current);
validateInteger(current)
anchor = object?.[current];
} else {
anchor = object?.[current];
}
if (isObject(anchor) || isArray(anchor)) {
return getValueViaPath(anchor, parts.join(DELIMITER), check)
}
if (parts.length > 0) {
throw Error("the journey is not at its end (" + parts.join(DELIMITER) + ")");
}
if (check === true && !object.hasOwnProperty(current)) {
throw Error('unknown value');
}
return anchor;
}
throw TypeError("unsupported type")
}
/**
*
* @param object
* @param path
* @param value
* @returns {undefined|*}
* @throws {TypeError} unsupported type
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
*/
function setValueViaPath(object, path, value) {
let parts = path.split(DELIMITER)
let last = parts.pop();
let subpath = parts.join(DELIMITER);
let stack = new Stack()
let current = subpath;
while (true) {
try {
getValueViaPath(object, current, true)
break;
} catch (e) {
}
stack.push(current);
parts.pop();
current = parts.join(DELIMITER);
if (current === "") break;
}
while (!stack.isEmpty()) {
current = stack.pop();
let obj = {};
if (!stack.isEmpty()) {
let n = stack.peek().split(DELIMITER).pop();
if (isInteger(parseInt(n))) {
obj = [];
}
}
setValueViaPath(object, current, obj);
}
let anchor = getValueViaPath(object, subpath);
if (!isObject(object) && !isArray(object)) {
throw TypeError("unsupported type: " + typeof object);
}
if (anchor instanceof Map) {
anchor.set(last, value);
} else if (anchor instanceof Set) {
anchor.append(value)
} else if (isArray(anchor)) {
last = parseInt(last);
validateInteger(last)
anchor[last] = value;
} else {
anchor[last] = value;
}
return anchor;
}