Suppose I have an object
const obj = {
key1:{
key2:{
val:'value'
}
}
}
Also I have some function that returns the path to the proper value:
func(obj,'value') //returns a string ['key1']['key2']['val']
I would like to get the 'value' with a call kind of this:
obj`${func(obj,'value')}`
It should look like
obj.key1.key2.val
But I am getting en error.
The Complexity of this stuff is for pushing the 'value' in an array with saving the tree structure
Using reduce() to return value by iterating an array of keys.
const obj = {key1:{key2:{val:'The retrieved value'}}}
// without eval()
const keyString = "['key1']['key2']['val']";
// return array of keys using regex
const keyReg = /\['(.+?)'\]/g;
const keyArr = [...keyString.matchAll(keyReg)].map(match => match[1]);
// passing obj as inital accumulator and iteratively retrieving nested value
const retrievedValue = keyArr.reduce((a, key) => (a = a[key], a),obj);
console.log(retrievedValue);
Using eval()
This is trivial with eval() but as MDN notes:
Warning: Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval(). See Never use eval()!, below.
const obj = {key1:{key2:{val:'The retrieved value'}}}
// Simply calling 'eval()' with the string
const keyString = "['key1']['key2']['val']";
const value1 = eval(`obj${keyString}`)
console.log(`obj${keyString}`)
console.log(value1);
// convoluted process to make it look like obj.key1.key2.val
const keyReg = /\['(.+?)'\]/g;
const keyArr = [...keyString.matchAll(keyReg)].map(match => match[1]);
const objQuery = keyArr.reduce((a, key) => (a += `.${key}`, a),`obj`);
const value2 = eval(objQuery)
console.log('');
console.log(objQuery)
console.log(value2);
// if you saved your initial string with a simple delimiter
const delimitedKeyString = "key1/key2/val";
const delimitedQuery = delimitedKeyString.split('/').join('.');
const value3 = eval(`obj.${delimitedQuery}`)
console.log('');
console.log(`obj.${delimitedQuery}`)
console.log(value3);
Related
There is a String variable that comes value like the following.
"COMPANY=10^INVOICE_ID=100021^"
I need to get this into two variables like Company and InvoieId.
What will be possible ways to do that JS?
Here is a dynamic way. You can grab any value from obj.
const str = "COMPANY=10^INVOICE_ID=100021^"
const obj = {}
const strArr = str.split('^')
strArr.forEach(str => {
const [key, value] = str.split('=')
obj[key] = value
})
const {COMPANY, INVOICE_ID } = obj
console.log(INVOICE_ID, COMPANY)
We can also do it via regular expression
Regex101 demo
let str = `COMPANY=10^INVOICE_ID=100021^`
let regex = /(\w+)=\d+/g
let match = regex.exec(str)
while(match){
console.log(match[1])
match = regex.exec(str)
}
If this code will be called more than once, let's create a function:
const parseInput = (input) => {
const [company, invoice_id] =
input.split("^")
.map(kv => kv.split("=")[1]);
return {
company,
invoice_id
};
}
I have two lists:
lista_source: 'B10L-A2,AABan38711$B10L-A2,AABan38811$B12A-A,AABan38912$B14-A2,AABan39314$B16B-A,AABan39616$B12A-A,AABan39818$B16L-B,AABan39919$B16L-B,AABan40019$B12A-A,AABan41112'
second_list: 'B10L-A2,B12A-A,B16L-B'
As a result I would like to get the following list (or similar one):
result = [B10L-A2:AABan38711,AABan38811],[B12A-A:AABan38912,AABan41112,AABan39818],[B16L-B:AABan39919,AABan40019]
In short, I'm looking for multiple values for the 2nd lists items.
I tried the filter function and write it to csv file but does not really work.
const first_list_object= first_list.split('$');
const second_list_object= second_list.split(',');
for (let i = 0; i < second_list_object.length; i++) {
let results= first_list_object.filter(x => x.includes(second_list_object[i]));
console.log(results);
writer = csvWriter({ sendHeaders: false });
writer.pipe(fs.createWriteStream(__dirname + '/lista.csv', { flags: 'a' }));
writer.write({
results
});
}
How should I solve it? Is there any better solution than filter?
A javascript object as output. If you need, I can convert this to .csv too.
const lista_source = 'B10L-A2,AABan38711$B10L-A2,AABan38811$B12A-A,AABan38912$B14-A2,AABan39314$B16B-A,AABan39616$B12A-A,AABan39818$B16L-B,AABan39919$B16L-B,AABan40019$B12A-A,AABan41112'
const second_list = 'B10L-A2,B12A-A,B16L-B'
// convert data to arrays
const source = lista_source.split(",")
const second = second_list.split(",")
// filter out source list items (into seperate object value) for each second list item
const res = second.reduce((obj, sec_key) => {
// get item, if string is not exact key name and string includes key name
const filtered = source.filter(key => key !== sec_key && key.includes(sec_key))
return ({...obj, [sec_key]: filtered })
}, {})
console.log(res)
Assuming that structure of the strings are gonna be like in question, I wrote same basic regex to split on and then add them accordingly to object. See if that's what you want.
Edit:
After rereading your question, I realized that comma actually doesn't separate values in your string but dolar sign instead (kinda weird but ok). I also added an if to take only values present in second list.
const lista_source = 'B10L-A2,AABan38711$B10L-A2,AABan38811$B12A-A,AABan38912$B14-A2,AABan39314$B16B-A,AABan39616$B12A-A,AABan39818$B16L-B,AABan39919$B16L-B,AABan40019$B12A-A,AABan41112'
const second_list = 'B10L-A2,B12A-A,B16L-B'.split(',')
const array = lista_source.match(/B[0-9]{2}[A-Z]-[A-Z][0-9]?,[A-Za-z0-9_]{10}/g)
let result = {}
for (let value of array) {
let [key, s] = value.split(',')
// only take keys form contained in second list
if(!second_list.includes(key)){
continue
}
key in result ? result[key] = [s, ...result[key]] : result[key] = [s]
}
console.log(result)
I need to pass an argument, that is either an array of one type or the type on its own, to a function that requires the only argument be an array:
// myFunction throws an error if the first argument is not an array
const foo = typeOrArrayOfType => myFunction([...typeOrArrayOfType, oneDefault]);
However if I do this:
const anArray = ['oneItem', 'twoItem'];
const notAnArray = 'oneItem';
const nonIterator = 300;
const oneDefault = 20;
const foo = typeOrArrayOfType => console.log([...typeOrArrayOfType, oneDefault]);
foo(anArray) // prints array items
foo(notAnArray) // spreads the string
foo(nonIterator) // error
It either works, spreads the string into characters or breaks entirely.
How can I flexibly take arguments that may or may not be an array?
I know about Array.isArray but I don't want helper function or conditionals if possible. I don't care about nested arrays, but if they behave differently it would be worth a note about it.
Create an array only if necessary
Declare a simple helper that wraps non-array values in an array or leaves the original intact:
const toArray = data =>
Array.isArray(data)
? data
: [data];
Example:
const anArray = ['oneItem', 'twoItem'];
const notAnArray = 'oneItem';
const nonIterator = 300;
const oneDefault = 20;
const toArray = data =>
Array.isArray(data)
? data
: [data];
const foo = typeOrArrayOfType => console.log([...toArray(typeOrArrayOfType), oneDefault]);
foo(anArray) // works
foo(notAnArray) // works
foo(nonIterator) // works
Convert everything to new arrays
The above has a slight weakness - it returns the original array in some cases. Which means that mutations might affect it:
const toArray = data =>
Array.isArray(data)
? data
: [data];
function test(input) {
const arrOutput = toArray(input);
arrOutput.push("world");
return arrOutput;
}
const arrInput = ["hello"];
const output = test(arrInput);
console.log(output); // [ "hello", "world" ]
console.log(arrInput); // [ "hello", "world" ]
To handle this, you could copy every array uniformly using Array#concat() - if given an array, it will produce a new array with a copy of its contents (only one level), if given non-array it will create a new array with the argument(s) as item(s):
const toArray = data =>
[].concat(data);
Example:
const anArray = ['oneItem', 'twoItem'];
const notAnArray = 'oneItem';
const nonIterator = 300;
const oneDefault = 20;
const toArray = data =>
[].concat(data);
const foo = typeOrArrayOfType => console.log([...toArray(typeOrArrayOfType), oneDefault]);
foo(anArray) // works
foo(notAnArray) // works
foo(nonIterator) // works
Example which does not have a problem with shared arrays:
const toArray = data =>
[].concat(data);
function test(input) {
const arrOutput = toArray(input);
arrOutput.push("world");
return arrOutput;
}
const arrInput = ["hello"];
const output = test(arrInput);
console.log(output); // [ "hello", "world" ]
console.log(arrInput); // [ "hello" ]
This might be even simpler to use, since it removes the need to spread items. The .concat() method already creates a new array and accepts variable number of arguments, it can be used directly as a way to create a new array with extra items:
const anArray = ['oneItem', 'twoItem'];
const notAnArray = 'oneItem';
const nonIterator = 300;
const oneDefault = 20;
const foo = typeOrArrayOfType => console.log([].concat(typeOrArrayOfType, oneDefault));
foo(anArray) // works
foo(notAnArray) // works
foo(nonIterator) // works
Note: the ##isConcatSpreadable well-known symbol property can affect how Array#concat() works. When set to true then concatinating the object will be "flatten" similar to how arrays are. This will work on any array-like. Conversely setting the property to false will prevent .concat() from spreading the object:
//make an object string
const str = new String('bar');
//make it spreadable
str[Symbol.isConcatSpreadable] = true;
console.log([].concat(str));
//a spreadable array-like:
const arrayLike = {
0: "h", 1: "e", 2: "l", 3: "l", 4: "o",
length: 5,
[Symbol.isConcatSpreadable]: true
};
console.log([].concat(arrayLike));
//non-spredable array:
const arr = ["x", "y", "z"];
arr[Symbol.isConcatSpreadable] = false;
console.log([].concat(arr));
.as-console-wrapper { max-height: 100% !important };
You should handle the variable by testing if it's an array with Array.isArray():
e.g.
const anArray = ['oneItem', 'twoItem'];
const notAnArray = 'oneItem';
const nonIterator = 300;
const oneDefault = 20;
const foo = typeOrArrayOfType =>
console.log(Array.isArray(typeOrArrayOfType)
? [...typeOrArrayOfType, oneDefault]
: [typeOrArrayOfType, oneDefault]);
foo(anArray) // prints array items
foo(notAnArray) // spreads the string
foo(nonIterator) // error
It would take very little effort to factorise the check into a utility function, which other answers do.
You can wrap it in an array and then use .flat() before spreading*
I've not seen this anywhere else on Stack Overflow.
This means that you always end up spreading an array:
const anArray = ['oneItem', 'twoItem'];
const notAnArray = 'oneItem';
const nonIterator = 300;
const nestedArray = ['lets', ['go', 'nest', ['arrays']]];
const anIterator = new Set([1,3,5,3]);
const oneDefault = 20;
const foo = typeOrArrayOfType => console.log([...[typeOrArrayOfType].flat(), oneDefault]);
foo(anArray) // nested array items is flattened one level
foo(notAnArray) // wrapped string doesn't get flattened
foo(nonIterator) // wrapped number doesn't get flattened
foo(nestedArray) // only flattens one level, added to show how .flat(works)
foo(anIterator) // this does not work, and results in {}
N.B. Using .flat() only goes down one level by default (unlike, say lodash.flattenDeep or some other libraries). You could optionally supply a number, but that is out of the scope of the question.
const foo = (typeOrArrayOfType, depth=1) => console.log([...[typeOrArrayOfType].flat(depth), oneDefault]);
And not useful for the situation in the question.
* As an aside, some of the other answers use [].concat(variable) which to me is as clear or slightly less clear, but is somewhat more performant, as per this jsbenchmark without spreading or with spreading.
I need to turn a string formatted like that:
string = "John:31,Miranda:28"
Onto this;
obj = { "John" => 31, "Miranda" => 28 }
I did this :
const class = new Map();
array = string.split(",");
And obviously I do not know what do with it because after the split I get something like this:
["John:31", "Miranda:28"]
And I don't know how to turn it onto an object (using the ":" as a arrow)... Maybe I don't need to use the array as an intermediary? Any thoughts? Thanks
You can use split to split by comma, and then map on the resulting strings to split again by colon, and feed the resulting array of arrays into the Map constructor.
For instance, if you want the map keyed by the names, which I suspect you do:
const string = "John:31,Miranda:28"
const map = new Map(string.split(",").map(entry => entry.split(":")));
console.log(map.get("John")); // "31" (a string)
If you want the numbers to be numbers, not strings, you'll need to convert them:
const string = "John:31,Miranda:28"
const map = new Map(string.split(",").map(entry => {
const parts = entry.split(":");
parts[1] = +parts[1];
return parts;
}));
console.log(map.get("John")); // 31 (a number)
My answer here goes into some detail on your options for converting from string to number.
If you want the map keyed by value instead (which I suspect you don't, but...), you just have to reverse the order of the inner array entries:
const string = "John:31,Miranda:28"
const map = new Map(string.split(",").map(entry => {
const [name, num] = entry.split(":");
return [num, name];
}));
console.log(map.get("31")); // John
So split on the commas, loop over it and split on the colon, and build the object.
var myString = "John:31,Miranda:28"
var myObj = myString.split(',').reduce(function (obj, part) {
var pieces = part.split(':')
obj[pieces[0]] = pieces[1]
return obj
}, {})
You could try something like this:
const data = "John:31,Miranda:28"
const splitData = data.split(',')
const result = splitData.reduce((newObject, item) => {
const [name, age] = item.split(':')
return {
...newObject,
[name]: parseInt(age)
}
}, {})
console.log(result)
I'll just add this here:
Basically, split string by the comma, then the colon.
Combine result into a map
const test = "John:31,Miranda:28";
console.log(test);
const obj = test.split(/,/).map(item => item.split(/:/));
console.log(obj);
const _map = new Map(obj);
console.log(_map);
console.log(_map.get("John"))
I have the below UploadDocument event which has fileReader.result which logs below message to the console
Output in the Console
A,B
aa,dsf
adfa,dfsd
fdsafds,sdf
uploadDocument(file) {
let fileReader = new FileReader();
fileReader.onload =(e) => {
console.log(fileReader.result);
}
fileReader.readAsText(this.file);
How can I split the above content (A,B...) in the console into a key value pairs ( Like a hashmap or Arraylist ) using javascript or Typescript?
I have tried splitting it
var lines = fileReader.result.split(/[\r\n]+/g);
console.log(lines);
Now how can I create A hashmap from this?
The reduce() array method can be used to build objects over each element, so we just need to split the file into multiple lines and then reduce the array of lines into an object.
var map = fileReader.result.split('\n')
.reduce((obj, line) => {
var cols = line.split(',');
// Tolerate empty lines. There may be one at the end of the input.
if (cols.length >= 2) {
obj[cols[0]] = cols[1];
}
return obj;
}, {});
Sample fiddle
One way would be to use reduce which takes an initial value - in this case an new object - and populates its keys and values with the result of splitting each line in the file on the comma with each iteration of the function.
const input = 'A,B\n\
aa,dsf\n\
adfa,dfsd\n\
fdsafds,sdf'
const obj = input.split(/\n/g).reduce((p, c) => {
const s = c.split(',');
p[s[0]] = s[1];
return p;
}, {});
console.log(obj)