I'm trying to prepare an object to POST to my server to store some information. This object requires me to do a few GET requests depending on how the user chooses to gather all the information needed to POST. I realized I have to modify the object to actually get them into the correct value pairs in JSON, and I'm not sure if there is a better way to do it.
I'm only showing this in a simple way, but the actual matter has 6-7 very long objects, and they all needs to be modified and fit in one JSON. The server API is written this way to accept input, and I don't have any say in it.
For example:
What I get back from requests
object1: {
id: 1,
name: "table",
price: 3499
}
object2: {
id: 5,
lat: 48.56,
lng: -93.45,
address: "1080 JavaScript Street"
}
What I need it to become:
data: {
map_id: 5,
product_id: [1],
product_name: ["table"],
product_price: [3499],
start_lat: 48.56,
start_lng: -93.45,
start_address: "1080 JavaScript Street"
}
So far I just do the dumb way to just stitch them together, I just wrote this on here so it doesn't work, but should show logically what I'm thinking:
prepareDataToSend = (object1, object2) => {
//exclude uninit handling, and newObject init for arrays
let newObject = {};
newObject.map_id = object2.id;
//if there are more of object1 then I have to loop it
newObject.product_id.push(object1.id);
newObject.product_name.push(object1.name);
...etc
}
I do get the result I'm looking for, but this feels really ineffective and dumb.Not to mention this seems very unmaintainable. Is there a better way to do this? I feel like there is some techniques i'm missing.
You could use ES6 object destructuring.
let object1 = {
id: 1,
name: "table",
price: 3499
};
let object2 = {
id: 5,
lat: 48.56,
lng: -93.45,
address: "1080 JavaScript Street"
};
// declaring the new object with the new properties names.
let newObject = {
map_id: '',
product_id: [],
product_name: [],
product_price: [],
start_lat: '',
start_lng: '',
start_address: ''
};
// destructuring "object1"
({id: newObject.product_id[0],
name: newObject.product_name[0],
price: newObject.product_price[0]} = object1);
// destructuring "object2"
({id: newObject.map_id,
lat: newObject.start_lat,
lng: newObject.start_lng,
address: newObject.start_address} = object2);
console.log(newObject)
Result:
{
map_id: 5,
product_id: [1],
product_name: ["table"],
product_price: [3499],
start_address: "1080 JavaScript Street",
start_lat: 48.56,
start_lng: -93.45
}
It sounds like you need something like JQuery's or Angular's extend() function, but with a twist for sub-mapping the keys.
https://api.jquery.com/jquery.extend/
Here's a simple version of it, tweaked for your needs.
//merge the N objects. Must have a "prefix" property to configure the new keys
var extendWithKeyPrefix = function() {
if (arguments.length == 0) return; //null check
var push = function(dst, arg) {
if (typeof arg != 'undefined' && arg != null) { //null check
var prefix = arg["prefix"]; //grab the prefix
if (typeof prefix != 'undefined' && prefix != null) { //null check
for (var k in arg) { //add everything except for "prefix"
if (k != "prefix") dst[prefix+k] = arg[k];
}
}
}
return dst;
}
arguments.reduce(push);
}
Please note that the value of the last object that uses a particular key will win. For example notice that "id" in the merged object is 2, rather than 1.
var object1 = {id: 1, unique1: "One", prefix: "product_"};
var object2 = {id: 2, unique2: "Two", prefix: "product_"};
var object3 = {id: 3, unique3: "Three", prefix: "office_"};
var merged = {};
extend(merged, object1, object2);
// value of merged is...
// { product_id: 2,
// product_unique1: "One",
// product_unique2: "Two",
// office_id: 3,
// office_unique3: "Three"
// }
Related
I'm stuck in this weird issue that I'm having hard time in understanding what's doing on. When a button is clicked it calls the function onSubmit. What onSubmit function should do is stringify is the object to JSON however for me this doesn't happen. The result I get when I console.log(JSON.stringify(obj)); is [[]]. When I console.log(obj); I can see the object.
I was not able to replicate the same issue in playcode.io and codesandbox.io
async function onSubmit() {
let l = [];
l["Channel"] = undefined;
l["MetricsData"] = [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
];
let obj = [];
l.Channel = 1;
obj.push(l);
console.log(obj);
console.log(JSON.stringify(obj)); //[[]]
}
As others have pointed in comments, you're instantiating l as an array and then attempt populating named keys (Channel, Metricsdata).
Note: Technically, arrays are objects, but they're special objects (their keys are intended to be numeric and they also have a few extra props and methods, specific to arrays - e.g: length, pop, push, slice, etc...). Use the link above to read more about arrays.
What you need to do is use l as an object (e.g: {}):
const l = {
Channel: 1,
MetricsData: [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
]
};
// Uncomment any of the following:
// console.log('object:', l);
// console.log('stringified object:', JSON.stringify(l));
const items = [];
items.push(l);
// console.log('items:', items);
// console.log('first item:', items[0]);
console.log(JSON.stringify(items));
A bit of a different use case from the ones I was suggested above.
I need to loop through and check each file name within an array of files and push the files that have the same name into a new array so that I can upload them later separately.
This is my code so far, and surely I have a problem with my conditional checking, can somebody see what I am doing wrong?
filesForStorage = [
{id: 12323, name: 'name', ...},
{id: 3123, name: 'abc', ...},
{id: 3213, name: 'name', ...},
...
]
filesForStorage.map((image, index) => {
for (let i = 0; i < filesForStorage.length; i++) {
for (let j = 0; j < filesForStorage.length; j++) {
if (
filesForStorage[i].name.split(".", 1) ===. //.split('.', 1) is to not keep in consideration the file extension
filesForStorage[j].name.split(".", 1)
) {
console.log(
"----FILES HAVE THE SAME NAME " +
filesForStorage[i] +
" " +
filesForStorage[j]
);
}
}
}
Using map without returning anything makes it near on pointless. You could use forEach but that is equally pointless when you're using a double loop within - it means you would be looping once in the foreach (or map in your case) and then twice more within making for eye-wateringly bad performance.
What you're really trying to do is group your items by name and then pick any group with more than 1 element
const filesForStorage = [
{id: 12323, name: 'name'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'name'}
]
const grouped = Object.values(
filesForStorage.reduce( (a,i) => {
a[i.name] = a[i.name] || [];
a[i.name].push(i);
return a;
},{})
);
console.log(grouped.filter(x => x.length>1).flat());
JavaScript has several functions which perform "hidden" iteration.
Object.values will iterate through an object of key-value pairs and collect all values in an array
Array.prototype.reduce will iterate through an array and perform a computation for each element and finally return a single value
Array.prototype.filter will iterate through an array and collect all elements that return true for a specified test
Array.prototype.flat will iterate through an array, concatenating each element to the next, to create a new flattened array
All of these methods are wasteful as you can compute a collection of duplicates using a single pass over the input array. Furthermore, array methods offer O(n) performance at best, compared to O(1) performance of Set or Map, making the choice of arrays for this kind of computation eye-wateringly bad -
function* duplicates (files) {
const seen = new Set()
for (const f of files) {
if (seen.has(f.name))
yield f
else
seen.add(f.name, f)
}
}
const filesForStorage = [
{id: 12323, name: 'foo'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'foo'},
{id: 4432, name: 'bar'},
{id: 5213, name: 'qux'},
{id: 5512, name: 'bar'},
]
for (const d of duplicates(filesForStorage))
console.log("duplicate name found", d)
duplicate name found {
"id": 3213,
"name": "foo"
}
duplicate name found {
"id": 5512,
"name": "bar"
}
A nested loop can be very expensive on performance, especially if your array will have a lot of values. Something like this would be much better.
filesForStorage = [
{ id: 12323, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'random' },
{ id: 3213, name: 'nothing' },
]
function sameName() {
let checkerObj = {};
let newArray = [];
filesForStorage.forEach(file => {
checkerObj[file.name] = (checkerObj[file.name] || 0) + 1;
});
Object.entries(checkerObj).forEach(([key, value]) => {
if (value > 1) {
newArray.push(key);
}
});
console.log(newArray);
}
sameName();
I am looking to delete a specific key from a nested Javascript object based on a list of dynamic properties. Here is an example of what I mean:
This is a sample object:
employees: [
{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
I am looking to delete the address key when I am provided an array of ['location', 'building', 'address']
When I say "dynamic" I mean that I could also be provided with an array of ['employees', 1] so I cannot rely on a set number of nested properties.
The only approach that works for me right now is to use the dreaded eval, which is not a permanent solution since the Javascript objects that I am reading are written by users.
let jsObject = ... // the object shown above
let properties = ['location', 'building', 'address']
let evalString = ''
for (let i = 0; i < properties.length; i++){
evalString += '[\''+properties[i]+'\']'
}
eval('delete jsObject'+evalString)
What is an alternative to eval that will accomplish this same goal?
You could reduce the object by the keys and save the last key for deleting the object with that key.
function deleteKey(object, keys) {
var last = keys.pop();
delete keys.reduce((o, k) => o[k], object)[last];
return object;
}
var object = { employees: [{ name: "John", id: '1234567890', salary: 60000 }, { name: "Jack", id: '0987654321', salary: 55000 }], location: { building: { address: "111 Main St" } } };
console.log(deleteKey(object, ['location', 'building', 'address']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This method accepts an object and an array of properties, and removes the inner most property as required
function remove(obj, props) {
delete props.slice(0, -1).reduce((init, curr) => init && init[curr], obj)[[...props].pop()];
}
You can break your array into everything except the last element, get a reference to that and the call delete on the object using the last element. You can use reduce to easily build the object reference. You need to be careful with arrays because you can't use delete without leaving an empty slot — delete doesn't change the length.
Here's the basic idea:
function deleteProp(obj, keys){
let prop = keys.pop() // get last key
let c = keys.reduce((a, c) => a[c], obj) // get penultimate obj
if (Array.isArray(c)) c.splice(prop, 1) // if it's an array, slice
else delete c[prop] // otherwise delete
}
// Delete address
let obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['location', 'building', 'address'])
console.log(obj)
//Delete employee 1
obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['employees', 1])
console.log(obj)
//Delete employee 1 id
obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['employees', 1, 'id'])
console.log(obj)
Here is a sample that i'm sure could be trimmed down a bit but it explains each step and you should be able to see whats happening in it:
let jsObject = {
employees: [{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
};
let properties = ['location', 'building', 'address'];
// we use this to traverse the object storing the parent
let parent = null;
// run over each property in the array
for (let i = 0; i < properties.length; i++) {
// check if this is the last property and we have the parent object
if (i + 1 == properties.length && parent)
delete parent[properties[i]]; // just delete the property from the object
else if (parent === null)
parent = jsObject[properties[i]] // set the initial parent
else
parent = parent[properties[i]] // set the parent to the property in the existing object
}
// log the output
console.log(jsObject);
You are going to want to throw error handling and checks to make sure you don't end up outside the object as well.
Navigate to the object that contains the property you want to delete, then delete it:
let jsObject = {
employees: [
{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
};
let properties = ['location', 'building', 'address'];
let innerMost = jsObject;
for (let i = 0; i < properties.length - 1; i++) {
if (typeof innerMost !== "object" || innerMost === null) {
innerMost = null;
break;
}
innerMost = innerMost[properties[i]];
};
if (
innerMost !== null &&
typeof innerMost === "object" &&
properties.length > 0 &&
innerMost.hasOwnProperty(properties[properties.length - 1])
) {
delete innerMost[properties[properties.length - 1]];
console.log(jsObject);
} else console.log("Invalid path");
I want to deep merge 2 javascript objects with similar properties in a reducer. The catch is the two objects have arrays as their properties and I need them to be concatenated instead of replaced. Basically something like this:
var x = {
name: 'abc',
family: {
children: [
{name: 'xxx'},
{name: 'zzz'}
]
}
};
var y = {
name: 'abc',
family: {
children: [
{name: 'yyy'}
]
}
};
will result on
var y = {
name: 'abc',
family: {
children: [
{name: 'xxx'}, {name: 'zzz'}, {name: 'yyy'}
]
}
};
any suggestion? I prefer lodash based solution if possible. Also, since it's in reducer, I cannot just replace the property with new object
objects have arrays as their properties and I need them to be
concatenated instead of replaced
For your simple example it would be enough to use Array.prototype.concat() function:
var x = {name: 'abc', family:{children:[{name: 'xxx'}, {name: 'zzz'} ] }}, y = {name: 'abc', family: {children: [{name: 'yyy'}]}};
y.family.children = y.family.children.concat(x.family.children);
console.log(y);
If you're already using lodash, check if mergeWith doesn't do what you want... (see this so answer for example).
Anyway here is a quick function that should do what you want. Be warned that it does very little in terms of checking, and only merges properties that are in the first object (since you said that your objects are similar).
Also it doesn't mutate the objects but instead creates a new one.
function merge(a, b){
var ret = {};
for(prop in a){
//assume b doesn't have properties that a hasn't
//only merge on similar types, else keep a
var propA = a[prop],
propB = b[prop];
console.log(prop, typeof propA)
if( (typeof propA == typeof propB) && (typeof propA == "object")){
if(Array.isArray(propA)){
ret[prop] = propA.concat(propB);
}else{
ret[prop] = merge(propA, propB);
}
}else{
ret[prop] = propA;
}
}
return ret;
}
var x = {
name: 'abc',
family: {
children: [
{name: 'xxx'},
{name: 'zzz'}
]
}
};
var y = {
name: 'abc',
family: {
children: [
{name: 'yyy'}
]
}
};
console.log(merge(x, y));
console.log(merge(y, x));
I assume one of your objects is the state you want to change in your reducer.
What about:
return Object.assign({}, x, {
family: [...x.family, ...y.family]
})
First this creates a new empty object and assigns x ( your old state ) to it. Afterwards it assigns the family which it overrides in the new object.
The ... represents all elements in an array, so this can be used to concat the arrays.
I created an immutable map (with Immutable-JS) from a list of objects:
var result = [{'id': 2}, {'id': 4}];
var map = Immutable.fromJS(result);
Now i want to get the object with id = 4.
Is there an easier way than this:
var object = map.filter(function(obj){
return obj.get('id') === 4
}).first();
Essentially, no: you're performing a list lookup by value, not by index, so it will always be a linear traversal.
An improvement would be to use find instead of filter:
var result = map.find(function(obj){return obj.get('id') === 4;});
The first thing to note is that you're not actually creating a map, you're creating a list:
var result = [{'id': 2}, {'id': 4}];
var map = Immutable.fromJS(result);
Immutable.Map.isMap(map); // false
Immutable.List.isList(map); // true
In order to create a map you can use a reviver argument in your toJS call (docs), but it's certainly not the most intuitive api, alternatively you can do something like:
// lets use letters rather than numbers as numbers get coerced to strings anyway
var result = [{'id': 'a'}, {'id': 'b'}];
var map = Immutable.Map(result.reduce(function(previous, current) {
previous[ current.id ] = current;
return previous;
}, {}));
Immutable.Map.isMap(map); // true
Now we have a proper Immutable.js map which has a get method
var item = Map.get('a'); // {id: 'a'}
It may be important to guarantee the order of the array. If that's the case:
Use an OrderedMap
Do a set method on the OrderedMap at each iteration of your source array
The example below uses "withMutations" for better performance.
var OrderedMap = Immutable.OrderedMap
// Get new OrderedMap
function getOm(arr) {
return OrderedMap().withMutations(map => {
arr.forEach(item => map.set(item.id, item))
})
}
// Source collection
var srcArray = [
{
id: 123,
value: 'foo'
},
{
id: 456,
value: 'bar'
}
]
var myOrderedMap = getOm(srcArray)
myOrderedMap.get(123)
// --> { id: 123, value: 'foo' }
myOrderedMap.toObject()
// --> { 123: {id: 123, value: 'foo'}, 456: {id: 456, value: 'bar'} }
myOrderedMap.toArray()
// --> [ {id: 123, value: 'foo'}, { id: 456, value: 'bar' } ]
When using fromJS for array, you'll get List not map. It will be better and easier if you create a map. The following code will convert the result into Immutable map.
const map = result.reduce((map, json) =>
map.set(json.id, Immutable.fromJS(json))
, Map());
Now, you can
map.get('2'); //{'id': 2}
Note, if the result has nested structure and if that has array, it will be a List with the above code.
With ES2015 syntax (and constants):
const result = map.find(o => o.get('id') === 4);
Is there already a way thats easier? I don't know. but you can write your own function. Something like this should work:
var myFunc = function(id){
var object = map.filter(function(obj){return obj.get('id') === id}).first();
return object;
}
Then you would just do:
var myObj = myFunc(4);