How to iterate using Symbol.iterator in ES6 - javascript

Currently I'm stuck in a lesson from an online course.
This is the question
//Implement an object named obj which has an Symbol.iterator property which iterates over every digit of the provided this.number.
const obj = {
number: 53820391,
[Symbol.iterator] () {
// TODO: implement me to print out all the digits of this.number
}
}
This is what I'm trying right now
const obj = {
number: 53820391,
[Symbol.iterator] () {
// TODO: implement me to print out all the digits of this.number
let cur = 0;
let a = this.number.toString();
let num = a.split();
return{
next() {
for( let char of num ){
return{
value: char,
done: cur++ > num.length
}
}
}
}
}
}
console.log(obj)
for(let char of obj){
char
}
I want to know where did I go wrong? and the process to solve this issue.

This would be the simplest implementation I could think of:
const obj = {
number: 53820391,
[Symbol.iterator]: function*() {
yield* this.number.toString();
}
}
Alternatively, you might implement it as
for (let d of this.number.toString()) {
yield d;
}
What's wrong with your code: next() function creates a new context, so this there refers to the new context, not to the original object.
To solve that you might keep a reference to the original object like const that = this; and use that reference wherever you need it.
References:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator

If you want to keep the basic format you have so you can see the inner workings, you are pretty close. You just don't want the for loop inside next(). The looping will happen by whoever is calling the iterator. So something like this works:
const obj = {
number: 53820391,
[Symbol.iterator]() {
const v = Array.from(this.number.toString())
return {
next: () => ({
done: v.length === 0,
value: v.shift()
})
}
}
}
for (const i of obj) {
console.log(i)
}

This was my solution.
const obj = {
number: 53820391,
[Symbol.iterator] () {
let index = 0
let numStr = this.number.toString()
return {
next() {
return {
value: numStr[index],
done: index++ >= numStr.length
}
}
}
}
}

Related

How to check how many times character from string appears?

I want to count how many times each letter from params appears in this string. What am I doing wrong?
function solution(word) {
let temp = {}
for(const c of word) {
temp[c]++;
console.log(temp[c])
}
}
solution("PAPAYA")
It should output me numbers below for each letter, but i keep getting NaN
1 // P appeared once
1 // A appeared once
2 // P appeared second time
2 // A appeaed second time
1 // Y Once
3 // A appeared third time
so it should look like
{
A: 3,
P: 2,
Y: 1
}
Here is an easy solution without changing your code to much
function solution(word) {
let temp = {}
for(const c of word) {
if (temp[c] === undefined)
temp[c] = 1;
else temp[c]++;
}
console.log(temp)
}
solution("PAPAYA")
At the start, no properties are set on the object, so accessing the value for a character results in undefined. Incrementing that produces NaN. You will need to specifically handle that case for the first time a letter appears.
function solution(word) {
let temp = {}
for(const c of word)
temp[c] = (temp[c] || 0) + 1;
return temp;
}
console.log(solution("PAPAYA"));
Easy solution using short-circuit in javascript:
function solution(word) {
let temp = {}
for(const c of word) {
temp[c] = temp[c]+1 || 1;
}
console.log(temp);
}
solution("PAPAYA")
The issue with your solution is that the first time the value is undefined so you were increasing undefined by 1, that's why you were getting NaN (Not A Number).
short-circuit will solve that, if it is not defined, start counting from one
set temp[c] to a number (0) first
for example
if (NaN(temp[c])) temp[c] = 0
The reason for this is because if you do ++ on undefined, it is still undefined, and therefore, not a number
const solution = (str) => {
const result = {}
str.split('').forEach(letter => {
result[letter] = result[letter] ? result[letter] + 1 : 1
})
return result
}
console.log(solution("PAPAYA"))
Simplest solution
function solution(word) {
let temp = {}
for(const c of word) {
if(c in temp)
temp[c]++;
else temp[c] = 1;
}
}
solution("PAPAYA")
Solution
let counter = str => {
return str.split('').reduce((total, letter) => {
total[letter] ? total[letter]++ : total[letter] = 1;
return total;
}, {});
};
console.log(counter("PAPAYA"));
//count
let unordered = counter("PAPAYA");
//sort the count object
const ordered = Object.keys(unordered).sort().reduce(
(obj, key) => {
obj[key] = unordered[key];
return obj;
},
{}
);
console.log(ordered);
function solution(word) {
let charMap = {};
word.split("").forEach((eachChar) => {
if (!charMap[eachChar]) {
charMap[eachChar] = 1;
} else {
charMap[eachChar] += 1;
}
});
console.log(charMap);
return charMap;
}
solution("ppiiyaaallll");

iteration associated object js

i want to use Symbol.iterator in object for iteration associated object
i need to return all key and all value in loop
this is my output :
let price = {
money:2000,
edit_photo:{
yes:100,
no:0
},
photo_type:{
personal:300,
sport:400,
fashion:500,
commercial:600
},
[Symbol.iterator](){
let items = Object.keys(this);
let step = 0;
return{
next(){
let object = {
done: step >= items.length,
value: items[step]
}
step++;
return object;
}
}
}
}
for (let item of price) {
console.log(item)
}
i have a problem to scroll all value
You formed your Symbol.iterator incorrectly. The Symbol.iterator is technically a generator and not an iterator. A generator function needs to be marked with a *.
const someObj = {
*[Symbol.iterator]() {
yield 'a';
yield 'b';
}
}
That way your object knows to actually use it as a generator function.
Now that we're using the right type of function, we have to return the right type of value. Well, yield the right type of value.
Since you are looking to return the key and value for each object item, the object we're yielding might looks like this:
yield {
done: false,
key: items[step],
value: this[items[step]]
}
To make sure we yield each item of the object, we put this inside of a while loop.
while (steps < items.length) {
yield { /* ... */ };
step++;
}
Once the while loop exits, we yield our final object.
yield {
done: true,
value: this,
};
The complete code based on your example is below. Citing my source: MDN here.
let price = {
money:2000,
edit_photo:{
yes:100,
no:0
},
photo_type:{
personal:300,
sport:400,
fashion:500,
commercial:600
},
*[Symbol.iterator](){
let items = Object.keys(this);
let step = 0;
while (step < items.length) {
yield {
done: false,
key: items[step],
value: this[items[step]]
};
step++;
}
yield {
done: true,
value: this,
}
}
}
for (let item of price) {
console.log(item);
}

How do I convert an environment variable to an object in JS?

I'm trying to convert an environment variable into an object of values for configuration in JavaScript but I don't know the best way to achieve this.
The idea would be to output SAMPLE_ENV_VAR=value as:
{
sample: {
env: {
var: value
}
}
}
What I have so far:
const _ = require('lodash');
const process = require('process');
_.each(process.env, (value, key) => {
key = key.toLowerCase().split('_');
// Convert to object here
}
Here's a more complete solution based on yours:
const _ = require('lodash');
const result = {};
// We'll take the following as an example:
// process.env = { HELLO_WORLD_HI: 5 }
// We'll expect the following output:
// result = { hello: { world: { hi: 5 } } }
_.each(process.env, (value, key) => {
// We'll separate each key every underscore.
// In simple terms, this will turn:
// "HELLLO_WORLD_HI" -> ['HELLO', 'WORLD', 'HI']
const keys = key.toLowerCase().split('_');
// We'll start on the top-level object
let current = result;
// We'll assign here the current "key" we're iterating on
// It will have the values:
// 'hello' (1st loop), 'world' (2nd), and 'hi' (last)
let currentKey;
// We'll iterate on every key. Moreover, we'll
// remove every key (starting from the first one: 'HELLO')
// and assign the removed key as our "currentKey".
// currentKey = 'hello', keys = ['world', 'hi']
// currentKey = 'world', keys = ['hi'], and so on..
while ( (currentKey = keys.shift()) ) {
// If we still have any keys to nest,
if ( keys.length ) {
// We'll assign that object property with an object value
// result =// { HELLO: {} }
current[currentKey] = {};
// And then move inside that object so
// could nest for the next loop
// 1st loop: { HELLO: { /*We're here*/ } }
// 2nd loop: { HELLO: { WORLD: { /*We're here*/ } } }
// 3rd loop: { HELLO: { WORLD: { HI : { /*We're here*/ } } } }
current = current[currentKey];
} else {
// Lastly, when we no longer have any key to nest
// e.g., we're past the third loop in our example
current[currentKey] = process.env[key]
}
}
});
console.log(result);
To simply put:
We'll loop through every environment variable (from process.env)
Split the key name with an underscore, and, again, loop each key (['HELLO', 'WORLD', 'HI'])
Assign it to an object ({ hello: {} } -> { hello: { world: {} } } -> { hello: world: { hi: ? } } })
When we no longer have any keys left, assign it to the actual value ({ hello: { world: { hi: 5 } } })
Funnily enough, I just finished code for this last night for a personal project. What I ended up using is not ideal, but is working for me:
export function keyReducer(
src: any,
out: any,
key: string,
pre: string,
del: string
): ConfigScope {
const path = key.toLowerCase().split(del);
if (path[0] === pre.toLowerCase()) {
path.shift();
}
if (path.length === 1) { // single element path
const [head] = path;
out[head] = src[key];
} else {
const tail = path.pop();
const target = path.reduce((parent: any, next: string) => {
if (parent[next]) {
return parent[next];
} else {
return (parent[next] = <ConfigScope>{});
}
}, out);
target[tail] = src[key];
}
return out;
}
static fromEnv(env: Environment, {prefix = 'ABC', delimiter = '_'} = {}) {
const data: ConfigScope = Object.keys(env).filter(key => {
return StringUtils.startsWith(key, prefix);
}).reduce((out, key) => {
return keyReducer(env, out, key, prefix, '_');
}, <ConfigScope>{});
return new Config(data);
}
(with TypeScript type annotations)
The idea here is to split each key, create the target objects on the way down, then set the final value.
This is my quick take at it:
var object = {}; // the object to store the value in
var name = "SAMPLE_ENV_VAR"; // the environment variable key
var value = "value"; // the value of the environment variable
// helper function to automatically create an inner object if none exists
function getOrCreateInnerObj(obj, name) {
if (!obj.hasOwnProperty()) {
obj[name] = {};
}
return obj[name];
}
// an array of the individual parts (e.g. ["sample", "env", "var"])
var keyParts = name.toLowerCase().split("_");
// innerObj will contain the second to last element object in the tree based on the array of keys
var innerObj = getOrCreateInnerObj(object, keyParts[0]);
for (var i = 1; i < keyParts.length - 1; i++) {
innerObj = getOrCreateInnerObj(innerObj, keyParts[i]);
}
// set the value itself
innerObj[keyParts[keyParts.length - 1]] = value;
$("body").html(JSON.stringify(object));
The gist of it is, for all but the last element in the array of key parts, you get or create an object in the current parent object for that key, and once you've repeated this for all but the last key, you'll have the second-to-last inner object, which you can then set the value on.
Edit: Working example
Edit 2: Here is a much cleaner example that uses a little bit of recursion to accomplish the same thing
const basic = {};
let current;
`YOUR_VARIABLE_NAME`
.split(`_`)
.forEach((item, index, array) => {
if(index === 0) {
return current = basic[item] = {};
}
if(index === array.length - 1) {
return current[item] = process.env.HE_LO_NA;
}
current = current[item] = {};
});
console.log(require('util').inspect(basic, {depth: 10}));
const _ = require('lodash');
const process = require('process');
const result = Object.entries(process.env).reduce((acc, [key, value]) => {
_.set(acc, key.toLowerCase().replace('_', '.'), value);
return acc;
}, {})

Defaultdict equivalent in javascript

In python you can have a defaultdict(int) which stores int as values. And if you try to do a 'get' on a key which is not present in the dictionary you get zero as default value.
Can you do the same in javascript/jquery
You can build one using a JavaScript Proxy
var defaultDict = new Proxy({}, {
get: (target, name) => name in target ? target[name] : 0
})
This lets you use the same syntax as normal objects when accessing properties.
defaultDict.a = 1
console.log(defaultDict.a) // 1
console.log(defaultDict.b) // 0
To clean it up a bit, you can wrap this in a constructor function, or perhaps use the class syntax.
class DefaultDict {
constructor(defaultVal) {
return new Proxy({}, {
get: (target, name) => name in target ? target[name] : defaultVal
})
}
}
const counts = new DefaultDict(0)
console.log(counts.c) // 0
EDIT: The above implementation only works well with primitives. It should handle objects too by taking a constructor function for the default value. Here is an implementation that should work with primitives and constructor functions alike.
class DefaultDict {
constructor(defaultInit) {
return new Proxy({}, {
get: (target, name) => name in target ?
target[name] :
(target[name] = typeof defaultInit === 'function' ?
new defaultInit().valueOf() :
defaultInit)
})
}
}
const counts = new DefaultDict(Number)
counts.c++
console.log(counts.c) // 1
const lists = new DefaultDict(Array)
lists.men.push('bob')
lists.women.push('alice')
console.log(lists.men) // ['bob']
console.log(lists.women) // ['alice']
console.log(lists.nonbinary) // []
Check out pycollections.js:
var collections = require('pycollections');
var dd = new collections.DefaultDict(function(){return 0});
console.log(dd.get('missing')); // 0
dd.setOneNewValue(987, function(currentValue) {
return currentValue + 1;
});
console.log(dd.items()); // [[987, 1], ['missing', 0]]
I don't think there is the equivalent but you can always write your own. The equivalent of a dictionary in javascript would be an object so you can write it like so
function defaultDict() {
this.get = function (key) {
if (this.hasOwnProperty(key)) {
return key;
} else {
return 0;
}
}
}
Then call it like so
var myDict = new defaultDict();
myDict[1] = 2;
myDict.get(1);
A quick dirty hack can be constructed using Proxy
function dict(factory, origin) {
return new Proxy({ ...origin }, {
get(dict, key) {
// Ensure that "missed" keys are set into
// The dictionary with default values
if (!dict.hasOwnProperty(key)) {
dict[key] = factory()
}
return dict[key]
}
})
}
So the following code:
n = dict(Number, [[0, 1], [1, 2], [2, 4]])
// Zero is the default value mapped into 3
assert(n[3] == 0)
// The key must be present after calling factory
assert(Object.keys(n).length == 4)
Proxies definitely make the syntax most Python-like, and there's a library called defaultdict2 that offers what seems like a pretty crisp and thorough proxy-based implementation that supports nested/recursive defaultdicts, something I really value and am missing in the other answers so far in this thread.
That said, I tend to prefer keeping JS a bit more "vanilla"/"native" using a function-based approach like this proof-of-concept:
class DefaultMap {
constructor(defaultFn) {
this.defaultFn = defaultFn;
this.root = new Map();
}
put(...keys) {
let map = this.root;
for (const key of keys.slice(0, -1)) {
map.has(key) || map.set(key, new Map());
map = map.get(key);
}
const key = keys[keys.length-1];
map.has(key) || map.set(key, this.defaultFn());
return {
set: setterFn => map.set(key, setterFn(map.get(key))),
mutate: mutationFn => mutationFn(map.get(key)),
};
}
get(...keys) {
let map = this.root;
for (const key of keys) {
map = map?.get(key);
}
return map;
}
}
// Try it:
const dm = new DefaultMap(() => []);
dm.put("foo").mutate(v => v.push(1, 2, 3));
dm.put("foo").mutate(v => v.push(4, 5));
console.log(dm.get("foo")); // [1, 2, 3, 4, 5]
dm.put("bar", "baz").mutate(v => v.push("a", "b"));
console.log(dm.get("bar", "baz")); // ["a", "b"]
dm.put("bar", "baz").set(v => 42);
console.log(dm.get("bar", "baz")); // 42
dm.put("bar", "baz").set(v => v + 1);
console.log(dm.get("bar", "baz")); // 43
The constructor of DefaultMap accepts a function that returns a default value for leaf nodes. The basic operations for the structure are put and get, the latter of which is self-explanatory. put generates a chain of nested keys and returns a pair of functions that let you mutate or set the leaf node at the end of these keys. Accessing .root gives you the underlying Map structure.
Feel free to leave a comment if I've overlooked any bugs or miss useful features and I'll toss it in.
Inspired by #Andy Carlson's answer, here's an implementation that works in a slightly more Pythonic way:
class DefaultDict {
constructor(defaultVal) {
return new Proxy(
{},
{
get: (target, name) => {
if (name == '__dict__') {
return target;
} else if (name in target) {
return target[name];
} else {
target[name] = defaultVal;
return defaultVal;
}
},
}
);
}
}
Basically, it also lets you retrieve all the gotten and set values of the "target", similar to how collections.defaultdict works in Python. This allows us to do things like:
const myDict = new DefaultDict(0);
myDict['a'] += 1;
myDict['b'] += 2;
myDict['c'] += 3;
myDict['whatever'];
console.log(myDict.__dict__);
// {'a': 1, 'b': 2, 'c': 3, 'whatever': 0}
To add to Andy Carlson's answer
If you default dict an array, you'll get a toJSON field in the resulting object. You can get rid of it by deconstructing to a new object.
const dd = new DefaultDict(Array);
//...populate the dict
return {...dd};
The original answer does not seem to work on the nested cases. I made some modifications to make it work:
class DefaultDict {
constructor(defaultInit) {
this.original = defaultInit;
return new Proxy({}, {
get: function (target, name) {
if (name in target) {
return target[name];
} else {
if (typeof defaultInit === "function") {
target[name] = new defaultInit().valueOf();
} else if (typeof defaultInit === "object") {
if (typeof defaultInit.original !== "undefined") {
target[name] = new DefaultDict(defaultInit.original);
} else {
target[name] = JSON.parse(JSON.stringify(defaultInit));
}
} else {
target[name] = defaultInit;
}
return target[name];
}
}
});
}
}
var a = new DefaultDict(Array);
a["banana"].push("ya");
var b = new DefaultDict(new DefaultDict(Array));
b["orange"]["apple"].push("yo");
var c = new DefaultDict(Number);
c["banana"] = 1;
var d = new DefaultDict([2]);
d["banana"].push(1);
var e = new DefaultDict(new DefaultDict(2));
e["orange"]["apple"] = 3;
var f = new DefaultDict(1);
f["banana"] = 2;
The difference is that if defaultInit is an object, we need to return a deep copy of the object, instead of the original one.

In javascript how can I dynamically get a nested property of an object

var arr = { foo : 1, bar: { baz : 2 }, bee : 3 }
function getter(variable) {
return arr[variable];
}
If I want 'foo' vs 'bee' I can just do arr[variable] - that's easy, and the function does that.
But what if I want to get arr.bar.baz AKA arr[bar][baz]?
What can I pass to the getter function that will let me do that, (and of course also let me get non-nested properties using the same function).
I tried getter('bar.baz') and getter('[bar][baz]') but those didn't work.
I suppose I can parse for dots or brackets (like here: In javascript, test for property deeply nested in object graph?). Is there a cleaner way? (Besides eval of course.)
Especially because I need to get the deeply set properly many many times in a loop for a bunch of array elements.
You can use a deep access function based on a string for the path. Note that you can't have any periods in the property names.
function getPropByString(obj, propString) {
if (!propString)
return obj;
var prop, props = propString.split('.');
for (var i = 0, iLen = props.length - 1; i < iLen; i++) {
prop = props[i];
var candidate = obj[prop];
if (candidate !== undefined) {
obj = candidate;
} else {
break;
}
}
return obj[props[i]];
}
var obj = {
foo: {
bar: {
baz: 'x'
}
}
};
console.log(getPropByString(obj, 'foo.bar.baz')); // x
console.log(getPropByString(obj, 'foo.bar.baz.buk')); // undefined
If the access string is empty, returns the object. Otherwise, keeps going along access path until second last accessor. If that's an ojbect, returns the last object[accessor] value. Otherwise, returns undefined.
Using ES6:
var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var {foo, bar, bar: {baz}, bee} = arr;
Same as:
// var foo = 1;
// var bar = {baz: 2};
// var baz = 2;
// var bee = 3;
Using lodash:
https://lodash.com/docs#get
_.get(arr, 'bar.baz'); //returns 2;
_.get(arr, 'bar.baz[5].bazzz'); //returns undefined wont throw error;
_.get(arr, 'bar.baz[5].bazzz', 'defaultvalue'); // Returns defaultValue because result is undefined
A recursive way :
function getValue(obj, path) {
if (!path) return obj;
const properties = path.split('.');
return getValue(obj[properties.shift()], properties.join('.'))
}
const myObj = {
foo: {
bar: {
value: 'good'
}
}
}
console.log(getValue(myObj, 'foo.bar.value')); // good
How about change the getter function signature as getter('bar', 'baz') instead
function getter() {
var v = arr;
for(var i=0; i< arguments.length; i++) {
if(!v) return null;
v = v[arguments[i]];
}
return v;
}
ps. didn't test, but you get the idea ;)
A one liner for you:
const mock = {
target: {
"prop1": {
"prop2": {
"prop3": "sad"
}
}
},
path: "prop1.prop2.prop3",
newValue: "happy"
};
mock.path.split(".").reduce(
(acc, curr, i, src) =>
(curr === src[src.length - 1]) ? acc[src[src.length - 1]] = mock.newValue : acc[curr], mock.target);
console.log(mock.target); //? { prop1: { prop2: { prop3: 'happy' } } }
Here's a very simple one liner which grants you dynamic access via "foo.bar.baz" mechanism,
var obj = {
foo: {
bar: {
baz: 'foobarbaz'
}
}
}
const nestedAccess = "foo.bar.baz";
console.log(nestedAccess.split('.').reduce((prev, cur) => prev[cur], obj)) //'foobarbaz'
I have recently developed my own Object method to get an object property nested among objects and arrays regardless how deep it is. It utilizes a single line of recursive approach. Check this out.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myObj = { foo : 1, bar: { baz : 2 }, bee : 3 },
bazval = myObj.getNestedValue("bar","baz");
document.write(bazval);
Now let's check a deeper nested array object combo data structure
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myArr = [{fox: [{turn:[857, 432]}]}, {sax: [{pana:[777, 987]}]}, {ton: [{joni:[123, 567]}]}, {piu: [{burn:[666, 37]}]}, {sia: [{foxy:[404, 696]}]}];
document.write(myArr.getNestedValue(3,"piu",0,"burn",1));
I believe being able to pass search parameters dynamically to existing array methods would make actions like searching, filtering or replacing of deeply nested structures much easy.
Using reduce we can fetch the value in single line of code.
const testobj = {b:{c:'1', d:{e:'2',f:'3'}}, g:{h:'3'}}
function fetchByDotOperator(object, value) {
return value.split('.').reduce((acc, curr) => acc[curr], object);
}
console.log(fetchByDotOperator(testobj,'b.d.e'))
You can access the functions arguments where you can pass any number of strings.
I also recommend using arr as a parameter for better encapsulation:
function getter() {
var current = arguments[0];
for(var i = 1; i < arguments.length; i++) {
if(current[arguments[i]]) {
current = current[arguments[i]];
} else {
return null;
}
}
return current;
}
var arr = { foo : 1, bar: { baz : 2 }, bee : 3 };
var baz = getter(arr, 'bar', 'baz');
function getPropertyByString(object, propString) {
let value = object;
const props = propString.split('.');
for (let index = 0; index < props.length; index += 1) {
if (props[index] === undefined) break;
value = value[props[index]];
}
return value;
};
const object = {
name: 'any_name',
address: {
number: 77,
test: {
name: 'test'
}
}
}
console.log(getPropertyByString(object, 'address.test.name'))
// test
Above answers help you access nested objects only, however you might also want to access data in an object/array data type. You can try this recusive method:
const getValue = (obj, key) => {
const keyParts = key.split(".");
return getValueHelper(obj, keyParts);
};
const getValueHelper = (obj, keyParts) => {
if (keyParts.length == 0) return obj;
let key = keyParts.shift();
if (Array.isArray(obj[key])) {
return obj[key].map((x) => getValueHelper(x, [...keyParts])).flat();
}
return getValueHelper(obj[key], [...keyParts]);
};
//Examples
let data1 = {
a: [{ b: { c: [{ d: [{ e: 1 }] }] } }, { b: { c: [{ d: [{ e: 2 }] }] } }],
};
console.log(getValue(data1, "a.b.c.d.e"));
//Output
//[ 1, 2 ]
let data2 = {
a:{b:1},
};
console.log(getValue(data2, "a.b"));
//Output
//1
p.s. Remove .flat() to get desired output for arrays.
Theres a function defined on this blog to safely read nested properties from a JS object
It allows you to mine an object for properties... ie.
safeRead(arr, 'foo', 'bar', 'baz');
and if any part of the object chain is null or undefined it returns an empty string....
let obj = {foo : {bar: {baz:1}}};
// -- simply
console.log(eval('obj.foo.bar.baz')); //-- 1
// -- safer
val = "";
try {
val = eval('Obj.foo.bar.baz')
}
catch(e) {
val = "empty"
}
// -- val = 1
// -- use at your risk ;)
Here I created a small suite of functions to 'get / 'set' / 'push' / 'pull' from object nested properties.
inputObject : Target object.
Ex: obj = {a:1, b:{c:2,d:3}}
propertyString : String containing the key to access.
Ex: "b.c"
Finally:
_getObjectValueByPathString(obj, "b.c") would return 2
function _getObjectValueByPathString(inputObject, propertyString) {
let splitStr = propertyString.split('.');
if (!inputObject.hasOwnProperty(splitStr[0])) return undefined;
if (splitStr.length === 1) {
return inputObject[splitStr[0]];
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
return _getObjectValueByPathString(inputObject[firstValue], newPropertyString);
}
else {
throw "Invalid property string provided";
}
}
function _setObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]] = inputValue;
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_setObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}
function _pushObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]].push(inputValue);
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_pushObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}
function _pullObjectValueByPathString(inputObject, propertyString, inputValue) {
let splitStr = propertyString.split('.');
if (splitStr.length === 1) {
inputObject[splitStr[0]].pull(inputValue);
return;
}
else if (splitStr.length > 1) {
let newPropertyString = "";
let firstValue = splitStr.shift();
splitStr.forEach((subStr, i) => {
newPropertyString = i === 0 ? subStr : newPropertyString.concat(`.${subStr}`);
});
_pullObjectValueByPathString(inputObject[firstValue], newPropertyString, inputValue);
return;
}
else {
throw "Invalid property string provided";
}
}

Categories

Resources