How to convert string value in an object to a number? - javascript

I have an object where the number values for the user_id are strings but I want to change these to return as numbers, how can I do this?
const users =
[
{ user_id: '1', name: 'Jack'},
{ user_id: '2', name: 'Emma'},
{ user_id: '3', name: 'David'}
]
// I want the output to look like this
const users =
[
{ user_id: 1, name: 'Jack'},
{ user_id: 2, name: 'Emma'},
{ user_id: 3, name: 'David'}
]

Inplace
users.forEach(u=>u.user_id*=1)
const users =
[
{ user_id: '1', name: 'Jack'},
{ user_id: '2', name: 'Emma'},
{ user_id: '3', name: 'David'}
]
users.forEach(u=>u.user_id*=1)
console.log(users);

Use a simple map, and use the unary + operator to convert to numbers.
const users = [{
user_id: '1',
name: 'Jack'
},
{
user_id: '2',
name: 'Emma'
},
{
user_id: '3',
name: 'David'
}
];
const res = users.map(({ name, user_id }) => ({ name, user_id: +user_id }));
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: auto; }
ES5 syntax:
var users = [{
user_id: '1',
name: 'Jack'
},
{
user_id: '2',
name: 'Emma'
},
{
user_id: '3',
name: 'David'
}
];
var res = users.map(function(user) {
return { name: user.name, user_id: +user.user_id };
});
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: auto; }

return users.map(user => ({...user, user_id: +user.user_id}))

May be like:-
let users =
[
{ user_id: '1', name: 'Jack'},
{ user_id: '2', name: 'Emma'},
{ user_id: '3', name: 'David'}
];
users = users.map(a => {
a.user_id = +a.user_id;
return a;
});
console.log(users);

I know this question already has an answer, but I wanted to offer an alternative solution that you may find beneficial and I'll explain why.
Answer:
Instead of utilizing a typical transform on the users array, you can apply a Proxy to each object and utilize the get handler to test if a property's value is able to be coerced to a number by using the + operator. This is done using a short-circuit evaluation, which returns the first true expression. If a property's value cannot be turned into a number, it will return the original value with no coercion( in this case, a string ).
users = users.map(_ =>
new Proxy(_, {get: (target, property) => +target[property] || target[property]}))
Why is this useful?
Code will always rely on your use-case. In your question you asked how to convert user_id to a number within your users object. If this is a one-off situation where you only need this specific property to be coerced to a numeric value, applying a proxy to your objects would not be recommended.
however
If you find yourself in a situation where you have a number of property values that you would like converted to number types if possible, you may consider using the above technique so that you don't have to pinpoint and update each property name within a separate conversion function.
Note: Since we are using a map method to bundle our original objects with a Proxy handler,you would either need to use a different declarative (var or let), or use Array.from in your original users declaration:
const users = Array.from(
[
{ user_id: '1', name: 'Jack'},
{ user_id: '2', name: 'Emma'},
{ user_id: '3', name: 'David'}
], _ => new Proxy(_, {get: (target, property) => +target[property] || target[property]}));
Code Example:
let users =
[{
user_id: '1',
name: 'Jack'
},
{
user_id: '2',
name: 'Emma'
},
{
user_id: '3',
name: 'David'
}
];
users = users.map(_ => new Proxy(_, { get: (target, property) => +target[property] || target[property]}))
users.forEach(({user_id, name}) => (console.log("user id: " + user_id + " is type: " + typeof user_id), console.log("name: " + name + " is type: " + typeof name)));

Related

How to return the data in one function call for two different object keys

I have an object which looks like this :
const data = {
students: [{
code: '1',
number: '22',
type: 'regular',
name: 'john',
age: '11',
class: 'A',
}, {
code: '2',
number: '23',
type: 'regular',
name: 'steve',
age: '12',
class: 'B',
}],
teachers: [{
code: '22',
number: '101',
type: 'intern',
name: 'mac',
}, {
code: '23',
number: '102',
type: 'perm',
name: 'jess',
}],
};
It has different keys and values.
Here, I am trying to massage this data so that I can obtain the following result: So I am trying to get an array which will have only students data and other which will have teachers data from one function itself.
const result1 = [{
code: '1',
number: '22',
type: 'regular',
name: 'john',
}, {
code: '2',
number: '23',
type: 'regular',
name: 'steve',
}];
const result2 = [{
code: '22',
number: '101',
type: 'intern',
name: 'mac',
}, {
code: '23',
number: '102',
type: 'perm',
name: 'jess',
}];
what I tried is :
const getData = ({data = []}) => {
data?.map({ code,
number,
regular,
name } ) => {
return{
code,
number,
regular,
name
}}
}
getData(data.students)
getData(data.teachers) // How do get this data in one function call itself
This gives me the result , but for this I need to call this function twice once for students and one for teachers. I want to call this function once.
Thanks
You could map new entries from the object and take the mapped new structure.
const
data = { students: [{ code: '1', number: '22', type: 'regular', name: 'john', age: '11', class: 'A' }, { code: '2', number: '23', type: 'regular', name: 'steve', age: '12', class: 'B' }], teachers: [{ code: '22', number: '101', type: 'intern', name: 'mac' }, { code: '23', number: '102', type: 'perm', name: 'jess' }] },
getData = ({ code, number, regular, name }) => ({ code, number, regular, name }),
result = Object.fromEntries(Object
.entries(data)
.map(([k, v]) => [k, v.map(getData)])
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Since from the provided data, it looks like the OP wants to mainly map a certain set of properties of student/teacher items from a map/object, a possible approach is to reduce the data object's entries and apply the mapping exclusively for a matching students or teachers key each referring to a valid array-type value.
const data = {
students: [{ code: "1", number: "22", type: "regular", name: "john", age: "11", class: "A" }, { code: "2", number: "23", type: "regular", name: "steve", age: "12", class: "B" }],
teachers: [{ code: "22", number: "101", type: "intern", name: "mac" }, { code: "23", number: "102", type: "perm", name: "jess" }],
};
const {
students: result1,
teachers: result2,
} = Object
.entries(data)
.reduce((result, [key, value]) => {
if (
// process only for a matching key ...
(/^students|teachers$/).test(key)
// ... and a valid value type.
&& Array.isArray(value)
) {
result[key] = value
.map(({ code, number, type, name }) =>
({ code, number, type, name })
);
}
return result
}, {});
console.log({ result1, result2 });
.as-console-wrapper { min-height: 100%!important; top: 0; }
I'm not sure what use there is in this, but since you want to run a function twice and get two results, do that, and combine into an object:
const data = {
students: [{
code: '1',
number: '22',
type: 'regular',
name: 'john',
age: '11',
class: 'A'
}, {
code: '2',
number: '23',
type: 'regular',
name: 'steve',
age: '12',
class: 'B'
}],
teachers: [{
code: '22',
number: '101',
type: 'intern',
name: 'mac'
}, {
code: '23',
number: '102',
type: 'perm',
name: 'jess'
}]
};
const transformData = (data = []) =>
data.map(({
code,
number,
regular,
name
}) => ({
code,
number,
regular,
name
}));
const getData = (data) =>
({
students: transformData(data.students),
teachers: transformData(data.teachers)
});
console.log(getData(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Note: I modified the transformData function to remove some extra syntax, and to remove the optional chaining since the Stack Snippets' ancient version of Babel doesn't support it.
Also, there's no property named regular on the original objects in the array, so they come out undefined.
Based on the input and output looks like you're just trying to slice off the keys age and class from the objects. So you'd just need to iterate through both the keys then the underlying objects to remove them.
const getData = (data) => {
Object.keys(data).forEach(function callback(key) {
let value = data[key];
value.forEach(function callback(obj) {
let blockArray = ['age', 'class'];
blockArray.forEach(e => delete obj[e]);
});
});
return data;
}
const updatedData = getData(data);
const result1 = updatedData.students;
console.dir(result1);
const result2 = updatedData.teachers;
console.dir(result2);

Getting parent node.name recursion is not working properly

I have an infinite tree:
const Data = [
{
id: '1',
name: 'hello',
children: [
{
id: '2',
name: 'world',
children: [
{
id: '3',
name: 'world',
children: [],
},
{
id: '4',
name: 'world',
children: [],
},
],
},
{
id: '5',
name: 'world',
children: [],
},
],
},
];
What I want to do is get the id and name of the path that leads to "world" and push it in to an array.
For example: the first path would be:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
]
second:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
]
And then push those arrays into another array.
So my result would look like this:
const result = [
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '4', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '5', name: 'world' },
],
];
I have a recursive function:
const findPath = (input="world", data, visitedStack, dataStack) => {
return data.map((node) => {
visitedStack.push({ id: node.id, name: node.name });
if (node.name.toLowerCase().includes(input.toLowerCase())) {
dataStack.push([...visitedStack]);
}
return findPath(
input,
node.children,
visitedStack,
dataStack
);
});
};
But this is adding on all the paths it has visited, so the last array that is pushed into dataStack will look like this:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
{ id: '4', name: 'world' },
{ id: '5', name: 'world' },
]
Not sure how to fix this. Or is this an incorrect approach?
The problem is that your visitedStack keeps growing, as you are eventually pushing all nodes unto it. Be aware that all recursive executions of your function get the same visitedStack to work with. So pushing [...visitedStack] is not going to push a path, but all nodes that had been visited before, which after a while do not represent a path any more.
If we stick with your function, then just make sure you don't push on visited permanently, but create a copy of that stack with the extra node, which will remain in the deeper recursion, but will not contaminate the whole rest of the execution. This way that extra node will not be there in the other, sibling paths:
const findPath = (input="world", data, visitedStack, dataStack) => {
return data.map((node) => {
let newStack = visitedStack.concat({ id: node.id, name: node.name });
if (node.name.toLowerCase().includes(input.toLowerCase())) {
dataStack.push(newStack);
}
return findPath(
input,
node.children,
newStack,
dataStack
);
});
};
Call as:
let result = [];
findPath("world", data, [], result);
console.log(result);
Alternative
I would however also address the following:
It is a bit odd that findPath does not return the result, but that the caller needs to provide the array in which the resulting paths should be collected. So I would suggest a function that returns the new array, not requiring the caller to pass that array as argument.
It is not useful to have a default value for a parameter, when other parameters following it, do not have a default value. Because, that means you anyway have to provide values for those other parameters, including the one that could have had a default value.
The paths that are returned still contain multiple references to the same objects. You do copy the objects into new objects, but as that new object sits in visitedStack, it will be reused when pushed potentially several times for deeper paths. So I would suggest making the object copies at the very last moment -- when the path is pushing on the result array.
Instead of repeatedly converting the input to lower case, do this only once.
Here is how you could write it:
function findPath(data, input="world") {
const result = [];
input = input.toLowerCase();
function recur(data, visitedStack) {
for (const node of data) {
const newStack = visitedStack.concat(node);
if (node.name.toLowerCase().includes(input)) {
result.push(newStack.map(o => ({id: o.id, name:o.name})));
}
recur(node.children, newStack);
}
}
recur(data, []);
return result;
}
const data = [{id: '1',name: 'hello',children: [{id: '2',name: 'world',children: [{id: '3',name: 'world',children: [],},{id: '4',name: 'world',children: [],},],},{id: '5',name: 'world',children: [],},],},];
const result = findPath(data);
console.log(result);

Why async.map returns multiple copies of array?

const async = require('async');
const arr = [
{ name: 'john', id: '1' },
{ name: 'Andrie', id: '2' }]
let collectArr = [];
let data = async.mapLimit(arr, 5, async function (input) {
collectArr.push({ name: input.name, id: input.id });
return collectArr;
})
data.then((result) =>{
console.log('success',result);
}).catch(e => console.log('err'));
So here i am providing array to async.mapLimit without callback and expecting promise here.
Expected Output :- [ { name: 'john', id: '1' }, { name: 'Andrie', id: '2' } ] ,
Got Result :-
[ [ { name: 'john', id: '1' }, { name: 'Andrie', id: '2' } ],
[ { name: 'john', id: '1' }, { name: 'Andrie', id: '2' } ] ]
So my question is why it is creating multiple copies of array, how to deal with this?
You are needlessly returning a sub array, and the same array reference each iteration, when all you want is to return the new object.
let data = async.mapLimit(arr, 5, async function (input) {
return { name: input.name, id: input.id };
});
Not sure why you need this to be async

Filtering array of objects if specific key contains search term

I am trying to filter through an object with multiple key/value pairs by a specific key. It appears that the code I've written is searching the entire object regardless of the key...
If key name contains the search term, return the search term.
Array of Objects:
export const someArrayOfObjects = [
{ id: '1', name: 'Something' },
{ id: '2', name: 'Another' },
{ id: '3', name: 'Lets do one more' },
]
Search:
const searchResults = someArrayOfObjects.filter((o) =>
Object.keys(o).some((k) => o[k].toString().toLowerCase().includes(searchTerm.toLowerCase()))
);
So if I search "Something", I only want it to loop through name to search for that term...
You don't need the Object.keys loop.
const someArrayOfObjects = [
{ id: '1', name: 'Something' },
{ id: '2', name: 'Another' },
{ id: '3', name: 'Lets do one more' },
];
let key = 'name';
let searchTerm = 'th';
const res = someArrayOfObjects.filter(o =>
o[key].toLowerCase().includes(searchTerm.toLowerCase()));
console.log(res);
similar to iota's, you don't need to create the extra array with Object.keys.
just loop/check every item inside the original array with the 'name' key.
you can also try to make it more reusable like below.
const someArrayOfObjects = [
{ id: '1', name: 'Something' },
{ id: '2', name: 'Another' },
{ id: '3', name: 'Lets do one more' },
];
const search = function (anyArray, searchTerm) {
return anyArray.filter((obj) => {
if (obj.name === searchTerm) {
return obj.name;
}
return false;
});
};
const case1 = search(someArrayOfObjects, 'Something');
console.log(case1);

How can I optimally group a list of objects by their sub object?

I'm trying to group some JavasScript objects by their shared similar object. I can do this effortlessly in Ruby, but for the life of my I (somewhat embarrassingly) can't figure this out in JS in linear time. JS doesn't seem to allow object literals as keys, at least for the purposes of reducing.
I have data shaped like this, as a result from a GraphQL query:
[
{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
]
In the UI, we're going to display the objects by the room they're in. We need to keep a reference to the room itself, otherwise we'd just sort by a room property.
What I'm trying to do is reshape the data into something like this:
{
{id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
{id: 3, name: 'Bathroom'}: [{id: 2, name: 'Tom'}]
}
As you can see, the people are grouped together by the room they're in.
It could also be shaped like this...
[
{ room: {id: 5, name: 'Kitchen'}, people: [{id: 1, name: 'Bob', ...}] },
{ room: {id: 3, name: 'Bathroom', people: [{id: 2, name: 'Tom'}]
]
However it comes out, we just need the people grouped by the rooms in linear time.
I've tried lodash's groupBy, using both map and reduce, just doing for loops that put the list together, etc. I'm stumped because without being able to use an object literal (the room) as a hash index, I don't know how to efficiently group the outer objects by the inner objects.
Any help is greatly appreciated.
Update: adding clarity about trying to do it with linear time complexity - the most efficient equivalent of this Ruby code:
h = Hash.new { |h, k| h[k] = [] }
value.each_with_object(h) { |v, m| m[v[:room]] << v }
You can solve this using lodash#groupBy and lodash#map to gather and transform each group. Additionally, we use lodash#omit to remove the room object from each person from the people array.
var result = _(data)
.groupBy('room.id')
.map(people => ({
room: { ...people[0].room },
people: _.map(people, person => _.omit(person, 'room'))
})).value();
var data = [
{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
];
var result = _(data)
.groupBy('room.id')
.map(people => ({
// make sure to create a new room object reference
// to avoid mutability
room: { ...people[0].room },
people: _.map(people, person => _.omit(person, 'room'))
})).value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You can use reduce to create an object of people indexed by rooms and then get that object's values, no library needed:
const input=[{id:1,name:'Bob',room:{id:5,name:'Kitchen'}},{id:3,name:'Sheila',room:{id:5,name:'Kitchen'}},{id:2,name:'Tom',room:{id:3,name:'Bathroom'}}]
const output = Object.values(
input.reduce((a, { id, name, room }) => {
const roomName = room.name;
if (!a[roomName]) a[roomName] = { room, people: [] };
a[roomName].people.push({ id, name });
return a;
}, {})
);
console.log(output);
Objects like
{id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
in your question can't be properties like that unless the structure is a Map. Ordinary Javascript objects can only have string (/ number) properties.
One alternative is to use reduce in order to groupBy the rooms.
const input = [{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
];
const res = input
.map(person => ({
person: {
id: person.id,
name: person.name
},
room: person.room
}))
.reduce((rooms, person) => {
const room = rooms.find(room => room.id === person.room.id) ||
{ room: person.room };
const idx = rooms.indexOf(room);
room.people = room.people ?
[...room.people, person.person] :
[person.person];
return Object.assign(rooms, {
[idx === -1 ? rooms.length : idx]: room
});
}, []);
console.log(res);

Categories

Resources