How to Implement Method Chaining - javascript

Is there a way to chain the following methods from Cache class
cache = cache.getKey(key) || cache.setKey(key).get(key);
// cache = cache.getKey(key) || cache.setKey(key).getKey(key);
I know we have native methods Map.prototype​.set() and Map.prototype​.get() but I want to implement it this way. Let me know if you have any suggestions.
function isObject(arg) {
const typeOfObj = typeof arg;
return (typeOfObj === 'object' || typeOfObj === 'function') && arg !== null;
}
class Cache {
constructor() {
this.map = new Map();
this.weakmap = new WeakMap();
}
setKey(key) {
const map = this[isObject(key) ? 'weakmap' : 'map'];
return map.set(key, new Cache());
}
getKey(key) {
const map = this[isObject(key) ? 'weakmap' : 'map'];
return map.get(key);
}
}
function getCache(args, cache) {
for (const key of args) {
cache = cache.getKey(key) || cache.setKey(key).get(key);
// cache = cache.getKey(key) || cache.setKey(key).getKey(key);
}
return cache;
}
function memoize(fn) {
const cache = new Cache();
return (...args) => {
const item = getCache(args, cache);
if (Reflect.has(item, 'value')) {
return item.value;
}
return (item.value = fn(args));
};
}
let counter = 1;
function foo() {
counter += 1;
return counter;
}
const id1 = Symbol('id');
const id2 = Symbol('id');
const memoizedFoo = memoize(foo);
console.log(memoizedFoo(id1)); // 2
console.log(memoizedFoo(id1)); // 2
console.log(memoizedFoo(id2)); // 3
console.log(memoizedFoo(id2)); // 3
Any help is greatly appreciated.

Unfortunately, if you're trying to "GET" a key, you cannot return the map, as you're asking for the key, so it must return the key. So you can't chain that method. But any method that shouldn't return something by default (IE would be void), we can do this:
cache = cache.getKey(key) || cache.setKey(key).get(key);
This will return a boolean, as the || symbol means return true if either is true.
But if you'd like to do the sort of chaining that JQuery does, that's easily achievable! What you want to do is return this or the object in every method.
Like so:
function isObject(arg) {
const typeOfObj = typeof arg;
return (typeOfObj === 'object' || typeOfObj === 'function') && arg !== null;
}
class Cache {
constructor() {
this.map = new Map();
this.weakmap = new WeakMap();
}
setKey(key) {
const map = this[isObject(key) ? 'weakmap' : 'map'];
map.set(key, new Cache());
return this; // HERE'S THE IMPORTANT PART
}
}
let counter = 1;
function foo() {
counter += 1;
return counter;
}
const id1 = Symbol('id');
const id2 = Symbol('id');
const memoizedFoo = memoize(foo);
console.log(memoizedFoo(id1)); // 2
console.log(memoizedFoo(id1)); // 2
console.log(memoizedFoo(id2)); // 3
console.log(memoizedFoo(id2)); // 3

Related

A way to convert an object with keys of . seperated strings into a JSON object

I'm trying to figure out a way to turn and object like this :
{ "test.subtest.pass" : "test passed", "test.subtest.fail" : "test failed" }
into JSON like this:
{ "test": { "subtest": { "pass": "test passed", "fail": "test failed" }}}
sometimes there may be duplicate keys, as above perhaps there would be another entry like "test.subtest.pass.mark"
I have tried using the following method and it works but it's incredibly ugly:
convertToJSONFormat() {
const objectToTranslate = require('<linkToFile>');
const resultMap = this.objectMap(objectToTranslate, (item: string) => item.split('.'));
let newMap:any = {};
for (const [key,value] of Object.entries(resultMap)) {
let previousValue = null;
// #ts-ignore
for (const item of value) {
// #ts-ignore
if (value.length === 1) {
if(!newMap.hasOwnProperty(item)) {
newMap[item] = key
} // #ts-ignore
} else if (item === value[value.length - 1]) {
if(typeof previousValue[item] === 'string' ) {
const newKey = previousValue[item].toLowerCase().replace(/\s/g, '');;
const newValue = previousValue[item];
previousValue[item] = {};
previousValue[item][newKey] = newValue;
previousValue[item][item] = key;
} else {
previousValue[item] = key;
}
} else if (previousValue === null) {
if (!newMap.hasOwnProperty(item)) {
newMap[item] = {};
}
previousValue = newMap[item];
} else {
if (!previousValue.hasOwnProperty(item)) {
previousValue[item] = {}
previousValue = previousValue[item];
} else if (typeof previousValue[item] === 'string') {
const newValue = previousValue[item];
previousValue[item] = {};
previousValue[item][item] = newValue;
} else {
previousValue = previousValue[item];
}
}
}
}
return newMap;
}
We can utilize recursion to make the code a little less verbose:
function convertToJSONFormat(objectToTranslate) {
// create root object for the conversion result
const result = {};
// iterate each key-value pair on the object to be converted
Object
.entries(objectToTranslate)
.forEach(([path, value]) => {
// utilize a recursive function to write the value into the result object
addArrayPathToObject(result, path.split("."), value);
});
return result;
}
function addArrayPathToObject(root, parts, value) {
const p = parts.shift();
// base-case: We attach the value if we reach the last path fragment
if (parts.length == 0) {
root[p] = value
return;
}
// general case: check if root[p] exists, otherwise create it and set as new root.
if(!root[p]) root[p] = {};
addArrayPathToObject(root[p], parts, value)
}
This function utilizes the fact that objects are pass-by-reference to recursively traverse through the object starting at its root until setting the desired value.
You can add error-handling and other such concerns as necessary for your use.
#Meggan Naude, toJson function copies json object to reference obj for provided keys and value.
const p = { "test.subtest.pass" : "test passed", "test.subtest.fail" : "test failed" };
const result = {} ;
const toJson = (obj, keys, value) => {
if (keys?.length === 1) {
obj[keys[0]] = value;
return obj
} else {
const k = keys.splice(0, 1)
if (k in obj) {
toJson(obj[k], keys, value)
} else {
obj[k] = {};
toJson(obj[k], keys, value)
}
return obj
}
}
Object.keys(p).forEach(key => toJson(result, key.split('.'), p[key]))
console.log(result);

Is it possible to detect at instantiation time all of a given type's callable methods and then dynamically create a forwarding process for each method

I needed a sort of aggregate class that forwards it's method calls to all of it's constituents. This is what I wrote:
class CompositeSpritesheet {
sprites = []; // collect Spritesheet objects
draw() {
this.sprites.forEach(s => (s.draw.apply(s, arguments)))
}
init() {
this.sprites.forEach(s => (s.init.apply(s, arguments)))
}
nextStep() {
this.sprites.forEach(s => (s.nextStep.apply(s, arguments)))
}
// ... for all possible functions
}
let board = new Spritesheet("a");
let text = new Spritesheet("b");
let composite = new CompositeSpritesheet();
composite.sprites.push(wood,text);
composite.draw(20,30);
This code is really repetitive, not exhaustive, might fail for unexpected function calls, and most importantly just really icky. Is there a way I can generalize this and maybe write a higher-order class that generates such compositions at whim?
In other words, is there a mechanism in JavaScript that lets me replicate the following pseudocode's behavior?
class CompositeSpritesheet{
sprites = [];
*("fn", args){
this.sprites.forEach(s => (s[fn]?.apply(s, arguments)))
}
}
This could be a dumb question, but I find JavaScript flexible in many unexpected ways that I haven't been able to completely wrap my head around. I feel like it could possible in some form or another.
If you have ideas that don't completely answer the question but are still related to the solution, feel free to post them as comments.
... something like this ..?
class CompositeSpritesheet {
constructor(...sprites) {
let spriteList = [].concat(...sprites);
this.addSprites = (...sprites) => {
spriteList = spriteList.concat(...sprites);
};
this.execute = (actionName, ...args) => {
spriteList.forEach(sprite => {
const action = sprite[actionName];
if (typeof action === 'function') {
action.apply(sprite, args);
}
});
};
}
}
let board = new Spritesheet("a");
let text = new Spritesheet("b");
let composite = new CompositeSpritesheet();
composite.addSprites(board, text);
composite.execute('draw', 20, 30);
Edit / Second Iteration
The next provided approach is a generic one. Knowing that at any time every internally listed Spritesheet type does feature the same set of methods, a newly introduced CompositeSpritesheet type does now detect all accessible keys of its first listed Spritesheet element, either at construction time or as soon as such (an) element(s) will be concatenated to the composite type's internally managed spritesheet-list. Then, while iterating this key-list, for every found spritesheet-method a corresponding forwarder method gets created dynamically.
... working example code ...
function isBooleanValue(type) {
return (typeof type === 'boolean');
}
function isNumberValue(type) {
return (typeof type === 'number');
}
function isStringValue(type) {
return (typeof type === 'string');
}
function isSymbol(type) {
return (typeof type === 'symbol');
}
function isPrimitive(type) {
return (
isBooleanValue(type)
|| isNumberValue(type)
|| isStringValue(type)
|| isSymbol(type)
);
}
function isObject(type) {
return (!!type && (typeof type === 'object'));
}
function isFunction(type) {
const functionType = 'function';
return (
(typeof type === functionType)
&& (typeof type.call === functionType)
&& (typeof type.apply === functionType)
);
}
/**
* - recursively collect a list of a type’s accessible keys
* that also might be inherited, but that are neither keys
* of `Object.prototype` nor keys of `Function.prototype`.
*/
function getAllAccessiblePropertyNames(type, keyList) {
// default return value.
keyList = (keyList || []);
// accept primitive data types as well.
if (isPrimitive(type)) {
type = Object(type);
}
// undefined and null value are kept out.
if (isObject(type)) {
keyList = keyList.concat(
Object.keys(type)
).concat(
Object.getOwnPropertyNames(type)
);
const protoType = (isFunction(type.constructor) && type.constructor.prototype);
if (protoType && (protoType !== Object.prototype)) {
if (protoType === protoType.constructor.prototype) {
keyList = keyList.concat(
Object.keys(protoType)
).concat(
Object.getOwnPropertyNames(protoType)
);
} else {
keyList = getAllAccessiblePropertyNames(protoType, keyList);
}
}
const proto = type.__proto__;
if ((isObject(proto) || isFunction(proto)) && (proto !== Object.prototype)) {
if (proto === proto.__proto__) {
keyList = keyList.concat(
Object.keys(proto)
).concat(
Object.getOwnPropertyNames(proto)
);
} else {
keyList = getAllAccessiblePropertyNames(proto, keyList);
}
}
}
return [...(new Set(keyList))].filter(key => !(/^\d+$/).test(key));
}
function isEmptyList(type) {
return ((type = Object(type)) && ('length' in type) && (Array.from(type).length === 0));
}
function withSpritesheetForwarderMethods(sharedState) {
// guard.
if (sharedState.hasForwarderMethods || isEmptyList(sharedState.spritesheetList)) {
// either does already feature all methods or there is still nothing to work with.
return;
}
const compositeType = this;
const spritesheetType = sharedState.spritesheetList[0];
getAllAccessiblePropertyNames(spritesheetType).forEach((key) => {
if (isFunction(spritesheetType[key])) {
// apply spritesheet forwarder method.
compositeType[key] = function (...args) {
sharedState.spritesheetList.forEach(
(spritesheet) => spritesheet[key].apply(spritesheet, args)
);
}
}
});
sharedState.hasForwarderMethods = true;
}
function withSpritesheetListManagement(sharedState) {
const compositeType = this;
compositeType.addSpritesheets = (...spritesheets) => {
sharedState.spritesheetList = sharedState.spritesheetList.concat(...spritesheets);
// ... either applicable latest at concat time ...
withSpritesheetForwarderMethods.call(compositeType, sharedState);
};
// ... or (preferably) already applicable at construction time.
withSpritesheetForwarderMethods.call(compositeType, sharedState);
}
class CompositeSpritesheet {
constructor(...spritesheets) {
const compositeType = this;
const sharedState = {
hasForwarderMethods: false,
spritesheetList: [].concat(...spritesheets)
};
withSpritesheetListManagement.call(compositeType, sharedState);
}
}
class Spritesheet {
constructor(name) {
this.name = name;
}
draw(...args) {
console.log(`"${ this.name }" :: draw :: args : `, args);
}
}
// test 1 :: apply forwarder methods at construction time.
const board = new Spritesheet("board");
const text = new Spritesheet("text");
const forwardingAtConstructionTime = new CompositeSpritesheet([board, text]);
console.log('forwardingAtConstructionTime.draw : ', forwardingAtConstructionTime.draw);
forwardingAtConstructionTime.draw(20, 30);
// test 2 :: apply forwarder methods at concat time.
const wood = new Spritesheet("wood");
const paper = new Spritesheet("paper");
const forwardingAtConcatTime = new CompositeSpritesheet();
console.log('forwardingAtConcatTime.draw : ', forwardingAtConcatTime.draw);
forwardingAtConcatTime.addSpritesheets(wood, paper);
forwardingAtConcatTime.draw(50, 10);
console.log('forwardingAtConcatTime.draw : ', forwardingAtConcatTime.draw);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit / Third Iteration
The design of the 3rd iteration is much cleaner. The class implementation is straight forward. The glue code work of dynamically creating the forwarding actions is done by a factory named from with this factory also being the only method of its CompositeSpritesheet namespace. Thus, this factory needs any randomly chosen or ad hoc created Spritesheet type in order to then create an according CompositeSpritesheet type.
... working example code ...
function isBooleanValue(type) {
return (typeof type === 'boolean');
}
function isNumberValue(type) {
return (typeof type === 'number');
}
function isStringValue(type) {
return (typeof type === 'string');
}
function isSymbol(type) {
return (typeof type === 'symbol');
}
function isPrimitive(type) {
return (
isBooleanValue(type)
|| isNumberValue(type)
|| isStringValue(type)
|| isSymbol(type)
);
}
function isObject(type) {
return (!!type && (typeof type === 'object'));
}
function isFunction(type) {
const functionType = 'function';
return (
(typeof type === functionType)
&& (typeof type.call === functionType)
&& (typeof type.apply === functionType)
);
}
/**
* - recursively collect a list of a type’s accessible keys
* that also might be inherited, but that are neither keys
* of `Object.prototype` nor keys of `Function.prototype`.
*/
function getAllAccessiblePropertyNames(type, keyList) {
// default return value.
keyList = (keyList || []);
// accept primitive data types as well.
if (isPrimitive(type)) {
type = Object(type);
}
// undefined and null value are kept out.
if (isObject(type)) {
keyList = keyList.concat(
Object.keys(type)
).concat(
Object.getOwnPropertyNames(type)
);
const protoType = (isFunction(type.constructor) && type.constructor.prototype);
if (protoType && (protoType !== Object.prototype)) {
if (protoType === protoType.constructor.prototype) {
keyList = keyList.concat(
Object.keys(protoType)
).concat(
Object.getOwnPropertyNames(protoType)
);
} else {
keyList = getAllAccessiblePropertyNames(protoType, keyList);
}
}
const proto = type.__proto__;
if ((isObject(proto) || isFunction(proto)) && (proto !== Object.prototype)) {
if (proto === proto.__proto__) {
keyList = keyList.concat(
Object.keys(proto)
).concat(
Object.getOwnPropertyNames(proto)
);
} else {
keyList = getAllAccessiblePropertyNames(proto, keyList);
}
}
}
return [...(new Set(keyList))].filter(key => !(/^\d+$/).test(key));
}
const CompositeSpritesheet = (function () {
// module scope.
// lean class.
class CompositeSpritesheet {
// declare private instance field.
#list
constructor() {
// initialize private instance field.
this.#list = [];
}
// prototypal instance methods with private instance field access.
getSpritesheets() {
return [...this.#list];
}
addSpritesheets(...spritesheets) {
return [...(this.#list = this.#list.concat(...spritesheets))];
}
}
// creation helper.
function createForwardingAction(composite, key) {
composite[key] = function (...args) {
composite.getSpritesheets().forEach(
(spritesheet) => spritesheet[key].apply(spritesheet, args)
);
}
}
// factory.
function createCompositeFromSpritesheetType(dummySpritesheet) {
const composite = new CompositeSpritesheet();
getAllAccessiblePropertyNames(dummySpritesheet).forEach((key) => {
if (isFunction(dummySpritesheet[key])) {
// apply spritesheet forwarder method.
createForwardingAction(composite, key);
}
});
return composite;
}
// module export.
return {
from: createCompositeFromSpritesheetType
};
}());
class Spritesheet {
constructor(name) {
this.name = name;
}
draw(...args) {
console.log(`"${ this.name }" :: draw :: args : `, args);
}
}
// test
const composite = CompositeSpritesheet.from(new Spritesheet);
console.log('composite : ', composite);
console.log('get current spritesheet list : ', composite.getSpritesheets());
const board = new Spritesheet("board");
const text = new Spritesheet("text");
const wood = new Spritesheet("wood");
const paper = new Spritesheet("paper");
console.log('add [board, text] to list : ', composite.addSpritesheets(board, text));
composite.draw(20, 30);
composite.addSpritesheets([wood, paper]); // add via array is possible too.
console.log('get current spritesheet list : ', composite.getSpritesheets());
composite.draw(50, 10);
.as-console-wrapper { min-height: 100%!important; top: 0; }
EDIT:
Given the comments in the other answer, what you could do is to create your class and then add the functions to the prototype:
class CompositeSpritesheet {
sprites = [];
};
var functionsToCreate = ["draw", "init", "nextStep"];
for (let f of functionsToCreate) {
CompositeSpritesheet.prototype[f] = function() {
this.sprites.forEach(s => (s[f].apply(s, arguments)));
}
}
And so CompositeSpritesheet will have all the functions you want in its prototype and so you will be able to call composite.draw(20,30);.
The only thing you have to do is to fill the functionsToCreate array.
=====================
A solution I can see could be to call only one method with different arguments:
class CompositeSpritesheet {
function doSomething(action, args) {
this.sprites.forEach(s => (s[action].apply(s, args)));
}
So you can call composite.doSomething("draw", [20,30]);.

Moving method out of the Class

I'd like to re-factor Cache class purely for academic reason.
However, I'm having a tough time figuring out how I can move getMap out of the Cache class and have it as a regular function.
function isObject(arg) {
const typeOfObj = typeof arg;
return (typeOfObj === 'object' || typeOfObj === 'function') && arg !== null;
}
class Cache {
constructor() {
this.map = new Map();
this.weakmap = new WeakMap();
}
// return a Cache's value at a key
getMap(key) {
// console.log(this);
const map = this[isObject(key) ? 'weakmap' : 'map'];
// console.log(map);
this.setKeyIfNeeded(map, key);
let valueMap = map.get(key);
return valueMap;
}
// create a Cache's key, if needed
setKeyIfNeeded(map, key) {
if (!map.has(key)) {
map.set(key, new Cache());
}
}
}
const getNestedMap = (keys, initialCache) =>
keys.reduce((cache, key) => cache.getMap(key), initialCache);
function memoize(fn) {
const cache = new Cache();
return (...args) => {
// get (or create) a cache item
const item = getNestedMap(args, cache);
if (Reflect.has(item, 'value')) {
return item.value;
}
return (item.value = fn(args));
};
}
let counter = 1;
function foo() {
counter += 1;
return counter;
}
const id1 = Symbol('id');
const id2 = Symbol('id');
const memoizedFoo = memoize(foo);
console.log(memoizedFoo(3, 4, 5, 6)); //2
console.log(memoizedFoo(3, 4, 5, 6)); //2
console.log(memoizedFoo(3, 4, 6)); //3
console.log(memoizedFoo(3, 4, 6)); //3
You will need to rewrite your function to accept all things. Then you'd just call it from within your class.
One example might be:
getMap(cache, key, isObject, setKeyIfNeeded) {
// console.log(this);
const map = cache[isObject(key) ? 'weakmap' : 'map'];
// console.log(map);
setKeyIfNeeded(map, key);
let valueMap = map.get(key);
return valueMap;
}
SOLUTION
function isObject(arg) {
const typeOfObj = typeof arg;
return (typeOfObj === 'object' || typeOfObj === 'function') && arg !== null;
}
class Cache {
constructor() {
this.map = new Map();
this.weakmap = new WeakMap();
}
static setKey(key, map) {
return map.set(key, new Cache());
}
}
function getCache(args, cache) {
for (const key of args) {
const map = cache[isObject(key) ? 'weakmap' : 'map'];
cache = map.get(key) || Cache.setKey(key, map).get(key);
}
return cache;
}
function memoize(fn) {
const cache = new Cache();
return (...args) => {
const item = getCache(args, cache);
if (Reflect.has(item, 'value')) {
return item.value;
}
return (item.value = fn(args));
};
}
let counter = 1;
function foo() {
counter += 1;
return counter;
}

Why does immutability-helper need `nextObject = object` after checking `nextObject === object`?

The part of source code is:
getAllKeys(spec).forEach(function(key) {
if (hasOwnProperty.call(commands, key)) {
var objectWasNextObject = object === nextObject;
nextObject = commands[key](spec[key], nextObject, spec, object);
if (objectWasNextObject && update.isEquals(nextObject, object)) {
nextObject = object;
}
} else {
// ...
}
})
and the update.isEquals code is
update.isEquals = function(a, b) { return a === b; };
Why is nextObject = object; needed?
When isEquals returns true, nextObject equals object already.

How can I create empty JSON keys (nested or not) using a string?

I currently have this code built in JS, but it's really, really ugly.
Is there any better way to approach it?
The way it works basically is pushing a string like app.chat.test to be the key, and value like teststr.
I test the lengths to see if the "parent" key is there, otherwise we build it.
function constructJson(jsonKey, jsonValue){
//REWRITE!!!!!!!!
let jsonObj = langFile;
let jsonKeyArr = jsonKey.split('.')
if (jsonKeyArr.length === 1) {
if (valToAdd === undefined) {
if (jsonObj[jsonKey] === undefined) {
jsonObj[jsonKey] = {}
}
} else {
if (jsonObj[jsonKey] === undefined) {
jsonObj[jsonKey] = valToAdd
}
}
} else if (jsonKeyArr.length === 2) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = jsonValue
}
} else if (jsonKeyArr.length === 3) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] = jsonValue
}
} else if (jsonKeyArr.length === 4) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] = jsonValue
}
} else if (jsonKeyArr.length === 5) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]][jsonKeyArr[4]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]][jsonKeyArr[4]] = jsonValue
}
} else if (jsonKeyArr.length > 5) {
return console.log("Length over 5 not supported yet!")
}
return jsonObj;
}
Regards.
OF course it's possible, a simple loop will perfeclty do the job.
function constructJson(jsonKey, jsonValue){
//REWRITE!!!!!!!!
langFile = {a:{}, foo:{}};// remove this for your own code
var jsonObj = langFile;
var jsonKeyArr = jsonKey.split('.');
var currentValue = jsonObj;
for(var i = 0; i < jsonKeyArr.length;i++){
if(currentValue[jsonKeyArr[i]]===undefined){
currentValue[jsonKeyArr[i]] = {};
}
if(i < jsonKeyArr.length-1){
currentValue = currentValue[jsonKeyArr[i]];
}else{
currentValue[jsonKeyArr[i]] = jsonValue;
}
}
return jsonObj;
}
alert(JSON.stringify(constructJson("a.b.cd.ef", "toto")));
I just assigning to a temporary variable each sublevel. When i'm on the last i'm assigning the value.
Yes you can, using the javascript reduce function on the array created from the splitted string.
function namespaceCreateExceptLast(representationOfElementToCreate, baseNamespace) {
var tokens;
if (typeof representationOfElementToCreate !== 'string')
throw new Error('Expecting string as first parameter');
if (baseNamespace === undefined)
baseNamespace = window;
tokens = representationOfElementToCreate.split('.');
// Remove the last element (which will contain the value)
tokens.pop();
// Use reduce to create part by part your new object
return tokens.reduce(function (prev, property) {
if (typeof prev !== 'object') {
throw Error('One property is already defined but not an object, namespace creation has failed', property);
return undefined;
} else {
if (!prev[property])
prev[property] = {};
return prev[property];
}
}, baseNamespace);
};
Then you can have:
function constructJson(jsonKey, jsonValue){
let jsonObj = langFile;
var lastItem = namespaceCreateExceptLast(jsonKey, jsonObj);
var lastKey = jsonKey.substring(jsonKey.lastIndexOf('.') + 1);
lastItem[lastKey] = jsonValue;
}
I have added some comments and exceptions to help you understand how it's done, but it's mainly based on the reduce function which you can easily get help for (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce).

Categories