@socket-mesh/fleximap
Version:
A flexible hash map which supports deep keys.
386 lines (385 loc) • 11.7 kB
JavaScript
function isIterable(object) {
return object && (object.constructor.name === 'Object' || object instanceof Array);
}
function isInt(input) {
return /^[0-9]+$/.test(input);
}
export class FlexiMap {
constructor(object) {
this._data = [];
this.length = 0;
this.defaultAsArray = (object instanceof Array);
if (object) {
if (isIterable(object)) {
for (let i in object) {
if (object.hasOwnProperty(i)) {
if (isIterable(object[i])) {
this._data[i] = new FlexiMap(object[i]);
}
else {
this._data[i] = object[i];
}
}
}
}
else {
this._data.push(object);
}
}
}
getLength(keyChain) {
if (keyChain) {
return this.count(keyChain);
}
else {
return this._data.length;
}
}
_getValue(key) {
return this._data[key];
}
_setValue(key, value) {
this._data[key] = value;
}
_deleteValue(key) {
delete this._data[key];
}
getRaw(keyChain) {
if (!(keyChain instanceof Array)) {
keyChain = [keyChain];
}
const key = keyChain[0];
const data = this._getValue(key);
if (keyChain.length < 2) {
return data;
}
else {
if (data instanceof FlexiMap) {
return data.getRaw(keyChain.slice(1));
}
else {
return undefined;
}
}
}
get(keyChain) {
let result = this.getRaw(keyChain);
if (result instanceof FlexiMap) {
result = result.getAll();
}
return result;
}
getRange(keyChain, fromIndex, toIndex) {
const value = this.get(keyChain);
let range;
if (value instanceof Array) {
range = [];
if (toIndex == null || toIndex > value.length) {
toIndex = value.length;
}
for (var i = fromIndex; i < toIndex; i++) {
range.push(value[i]);
}
}
else {
range = {};
let recording = false;
if (value instanceof Object) {
for (let j in value) {
if (value.hasOwnProperty(j)) {
if (j === fromIndex.toString()) {
recording = true;
}
if (recording && j === toIndex.toString()) {
break;
}
if (recording) {
range[j] = value[j];
}
}
}
}
}
return range;
}
count(keyChain) {
const elements = this.get(keyChain);
if (elements) {
if (isIterable(elements)) {
let result = 0;
for (let i in elements) {
if (elements.hasOwnProperty(i)) {
result++;
}
}
return result;
}
return 1;
}
return 0;
}
hasImmediateKey(key) {
return this._data[key] !== undefined;
}
hasKey(keyChain) {
return (this.get(keyChain) === undefined) ? false : true;
}
hasType(keyChain, type) {
const objects = this.get(keyChain);
if (objects instanceof Object) {
for (let i in objects) {
if (objects.hasOwnProperty(i)) {
if (objects[i] instanceof type) {
return true;
}
}
}
}
return false;
}
hasValue(keyChain, value) {
const values = this.get(keyChain);
if (typeof values === 'object') {
for (let i in values) {
if (values.hasOwnProperty(i)) {
if (values[i] === value) {
return true;
}
}
}
}
return false;
}
hasObject(keyChain, object) {
const objects = this.get(keyChain);
if (typeof objects === 'object') {
for (var i in objects) {
if (objects.hasOwnProperty(i)) {
if (objects[i] === object) {
return true;
}
}
}
}
return false;
}
set(keyChain, value) {
const originalValue = value;
if (!(keyChain instanceof Array)) {
keyChain = [keyChain];
}
const key = keyChain[0];
if (keyChain.length < 2) {
if (!(value instanceof FlexiMap) && isIterable(value)) {
value = new FlexiMap(value);
}
this._setValue(key, value);
}
else {
if (!this.hasImmediateKey(key) || !(this._getValue(key) instanceof FlexiMap)) {
this._setValue(key, new FlexiMap());
}
this._getValue(key).set(keyChain.slice(1), value);
}
return originalValue;
}
add(keyChain, value) {
if (!(keyChain instanceof Array)) {
keyChain = [keyChain];
}
let insertionIndex;
let target = this.getRaw(keyChain);
if (target == null) {
insertionIndex = 0;
target = new FlexiMap([value]);
this.set(keyChain, target);
}
else if (!(target instanceof FlexiMap)) {
target = new FlexiMap([target, value]);
insertionIndex = target.getLength() - 1;
this.set(keyChain, target);
}
else {
insertionIndex = target.getLength();
this.set(keyChain.concat(insertionIndex.toString()), value);
}
return insertionIndex;
}
concat(keyChain, value) {
if (!(keyChain instanceof Array)) {
keyChain = [keyChain];
}
let target = this.getRaw(keyChain);
if (!isIterable(value)) {
value = [value];
}
if (!target) {
target = new FlexiMap(value);
this.set(keyChain, target);
}
else if (!(target instanceof FlexiMap)) {
target = new FlexiMap([target].concat(value));
this.set(keyChain, target);
}
else {
const keyChainLastIndex = keyChain.length;
if (value instanceof Array) {
const len = target.getLength();
keyChain = keyChain.concat(len.toString());
for (let i in value) {
if (value.hasOwnProperty(i)) {
this.set(keyChain, value[i]);
keyChain[keyChainLastIndex] = keyChain[keyChainLastIndex] + 1;
}
}
}
else {
for (let j in value) {
if (value.hasOwnProperty(j)) {
keyChain[keyChainLastIndex] = j;
this.set(keyChain, value[j]);
}
}
}
}
return value;
}
_remove(key) {
if (this.hasImmediateKey(key)) {
const data = this._getValue(key);
this._deleteValue(key);
if (data instanceof FlexiMap) {
return data.getAll();
}
return data;
}
return undefined;
}
remove(keyChain) {
if (!(keyChain instanceof Array)) {
keyChain = [keyChain];
}
if (keyChain.length < 2) {
return this._remove(keyChain[0]);
}
const parentMap = this.getRaw(keyChain.slice(0, -1));
if (parentMap instanceof FlexiMap) {
return parentMap._remove(keyChain[keyChain.length - 1]);
}
return undefined;
}
_splice(index, count, items) {
if (items) {
for (var j = 0; j < items.length; j++) {
if (isIterable(items[j])) {
items[j] = new FlexiMap(items[j]);
}
}
}
return this._data.splice(index, count, ...items);
}
splice(keyChain, index, count, ...items) {
const parentMap = this.getRaw(keyChain);
if (parentMap instanceof FlexiMap) {
const rawRemovedItems = parentMap._splice(index, count, items);
const plainRemovedItems = [];
let curItem;
const len = rawRemovedItems.length;
for (var j = 0; j < len; j++) {
curItem = rawRemovedItems[j];
if (curItem instanceof FlexiMap) {
plainRemovedItems.push(curItem.getAll());
}
else {
plainRemovedItems.push(curItem);
}
}
return plainRemovedItems;
}
return [];
}
removeRange(keyChain, fromIndex, toIndex) {
const value = this.get(keyChain);
let range;
if (value instanceof Array) {
if (toIndex == null || toIndex > value.length) {
toIndex = value.length;
}
range = value.splice(fromIndex, toIndex - fromIndex);
this.set(keyChain, value);
}
else {
range = {};
let deleting = false;
for (let i in value) {
if (value.hasOwnProperty(i)) {
if (i === fromIndex.toString()) {
deleting = true;
}
if (deleting && i === toIndex.toString()) {
break;
}
if (deleting) {
range[i] = value[i];
delete value[i];
}
}
}
this.set(keyChain, value);
}
return range;
}
pop(keyChain) {
return this.splice(keyChain, -1, 1);
}
removeAll() {
this._data = [];
}
_arrayToObject(array) {
const obj = {};
for (let i in array) {
if (array.hasOwnProperty(i)) {
obj[i] = array[i];
}
}
return obj;
}
getAll() {
let isArray = this.defaultAsArray;
const data = [];
for (let i in this._data) {
if (this._data.hasOwnProperty(i)) {
const value = this._data[i];
if (value instanceof FlexiMap) {
data[i] = value.getAll();
}
else {
data[i] = value;
}
}
}
if (isArray) {
const len = data.length;
for (let j = 0; j < len; j++) {
if (data[j] === undefined) {
isArray = false;
break;
}
}
}
if (isArray) {
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (!isInt(k)) {
isArray = false;
break;
}
}
}
}
if (isArray) {
return data;
}
return this._arrayToObject(data);
}
}