I receive an object like this:
this.data = {
O: {
id: 0,
name: value1,
organization: organization1,
...,
},
1: {
id: 1,
name: value1,
organization: organization1,
...,
},
2: {
id: 2,
name: value2,
organization: organization2,
...,
},
...
}
I then filter by id and remove the Object which id matches the id I receive from the store like so:
filterOutDeleted(ids: any[], data: object,) {
const remainingItems = Object.fromEntries(Object.entries(data)
.filter(([, item]) => !ids.some(id => id === item.id)));
const rows = Object.keys(remainingItems).map((item) => remainingItems[item]);
return rows;
}
Unfortunately, I'm getting an error when building stating Property 'fromEntries' does not exist on type 'ObjectConstructor' and I am unable to make changes in the tsconfig file at this point. Is there an alternative for fromEntries for this case? Any help is much appreciated!
Create the object outside instead, and for every entry that passes the test, assign it to the object manually.
Also note that you can decrease the computational complexity by constructing a Set of the ids in advance:
const filterOutDeleted = (ids: any[], data: object) => {
const idsSet = new Set(ids);
const newObj = {};
for (const [key, val] of Object.entries(data)) {
if (!idsSet.has(val.id)) {
newObj[key] = val;
}
}
return newObj;
};
Related
I want to merge values from an array into a static nested object. The array containing the values is something like this,
['name=ABC XYZ', 'hobbies=[M,N,O,P]', 'profession=S', 'age=27']
and the object in which the values has to be merged is,
const person = {
details_1: {
name: null,
hobbies: null,
profession: null
},
details_2: {
age: null
}
};
I want my output object to look like below,
const updated_person = {
details_1: {
name: 'ABC XYZ',
hobbies: [M,N,O,P],
profession: 'S'
},
details_2: {
age: 27
}
};
Thanks a lot for your help!
I made another solution with a different approach.
Here I used an interface weher I described the desired data structure.
In the second part the string array is tranformed into key and value pairs. Thereform are filtered the keys of interface and added into an empty object literal.
const data = ["name=ABC XYZ", "hobbies=[M,N,O,P]", "profession=S", "age=27"];
const dataInterface = {
details_1: { name: null, hobbies: null, profession: null },
details_2: { age: null },
};
function orederData(arr) {
const record = arr.map((item) => {
let [key, value] = item.split("=");
if (value[0] === "[" && value[value.length - 1] === "]") {
value = value.slice(1, value.length - 1).split(",");
}
return { key, value };
});
const dataBlock = {};
Object.keys(dataInterface).map((detail) => {
dataBlock[detail] = {};
Object.keys(dataInterface[detail]).forEach((dataKey) => {
dataBlock[detail][dataKey] = record.filter((record) => {
return record.key === dataKey;
})[0].value;
});
});
return dataBlock;
}
const orderedData = orederData(data);
console.log(orderedData);
You can simply achieve this by iterating the input array.
const arr = ['name=ABC XYZ', 'hobbies=[M,N,O,P]', 'profession=S', 'age=27'];
const person = {
details_1: {},
details_2: {}
};
arr.forEach(item => {
(item.split('=')[0] !== 'age') ? person.details_1[item.split('=')[0]] = item.split('=')[1] : person.details_2[item.split('=')[0]] = item.split('=')[1]
});
console.log(person);
There is no way to cleanly merge an unstructured array into a structured object such that the array values end up in the appropriately keyed person properties.
javascript does provide the assign() function that merges objects but for YOUR requirements your source data needs to be an object similarly structured and not an array.
so this:
['name=ABC XYZ', 'hobbies=[M,N,O,P]', 'profession=S', 'age=27']
would need to become this:
const source= [{details_1: {"name":"ABC XYZ", "hobbies":"[M,N,O,P]", "profession":"S"}, details_2: {"age":"27"}}]
such that a call to Object.assign():
const new_person = Object.assign(person, source[0]);
fills this
const person = {
details_1: {
name: null,
hobbies: null,
profession: null
},
details_2: {
age: null
}
};
properly, though you may need to clone or instantiate and empty person first.
or, if person is an Object you could have a fill() method that knows what to do with the array data.
I'm creating an online store. The product has attributes (in this case, iMac computer has attributes like: capacity, usb features and digital keyboard.)
I store the attributes in my state.
The problem is, every time I switch from, for example, 256GB capacity to 512GB capacity, it adds an entire new object with {capacity:"512GB"} to the array.
How do I configure my handler function (code below) so that it conditionally checks if the object in an array already has 'Capacity' key, and updates that to a new selection, instead of adding another object? I tried everything, I'm desperate
the handler receives object with key-value pair, and the key (as label) itself, and type. In this case, type can be ignored.
const handleAttributeChange = (object, type, label) => {
if (type == "text") {
this.setState({
selectedAttributes: {
id: id,
text: [...this.state.selectedAttributes.text, object],
swatch: this.state.selectedAttributes.swatch,
},
});
} else if ((type = "swatch")) {
this.setState({
selectedAttributes: {
id: id,
text: this.state.selectedAttributes.text,
swatch: [...this.state.selectedAttributes.swatch, object],
},
});
}
};
Instead of an array, you can take objects with diff key values. If keys are unique. Later u can convert it into the list of values.
let obj = {};
const update = (o) => {
obj = { ...obj, ...o };
};
/* In case you know key, value */
const update2 = (key, value) => {
obj[key] = value;
};
console.log(obj);
update({ a: 1 });
update({ b: 2 });
console.log(obj); // { a: 1, b: 2 }
update({ a: 3 });
console.log(obj); // { a: 3, b: 2 }
console.log(Object.entries(obj).map(([key, value]) => ({ [key]: value })));
//[ { a: 3 }, { b: 2 } ]
I hope someone can help me with my problem! I didn't find the right thing through the search and maybe someone can give me a hint.
I am calling an API that returns an object that in turn contains nested objects. In these nested objects there are two properties "value" and "scale". I want to divide these two properties and write them as properties in the same object.
The data I get from the API is dynamic, which means it is constantly changing.
Example:
// call api
const apiCall = callApi(...);
// return object
console.log(apiCall);
{
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
}
// targed object
const objTarged = {
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
result: 24.59 // value / scale = result
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
result: 0.1459 // value / scale = result
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
};
My thoughts:
do I need to map the object into a new object?
how can I do this if the source object is constantly changing (Object.values?)
How can I write the result of Value / Scale as a new property in the same object each time I call the API?
Big thanks in advance :)
It might be helpful to decompose the problem into first finding the nested objects with the keys you're interested in. Having done that, it will be easy to augment those objects with the desired calculation.
Below is a sort of generic function that finds a nested object based on it having a particular key. With that, fixMyApiData writes itself...
// return an array of objects that are nested in the passed object which contain the passed key
function objectsContainingKey(object, key) {
let results = [];
Object.keys(object).forEach(k => {
if (k === key) results.push(object);
if (object[k] && typeof object[k] === 'object')
results = results.concat(objectsContainingKey(object[k], key));
});
return results;
}
// find the nested objects we care about and augment them with the value/scale calculation
function fixMyApiData(apiData) {
objectsContainingKey(apiData, 'scale').forEach(data => {
if (data.value) data.result = data.value / data.scale;
})
}
let apiData = {
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
};
fixMyApiData(apiData);
console.log(apiData);
I would create a mapValues() function that takes an object, and creates a new object by passing each of the object's values in a transforming function.
Whenever the api call returns a new object, we recreate the new object with the result property according to the structure.
How does the mapValues function works?
Whenever an object (or array) is passed to mapValues, it's converted to an array of [key, value] pairs. The pairs are then mapped to new [key, pair] entries by applying transformFn to the value. The transform array of pairs is then converted back to an using Object.fromEntries().
const mapValues = (transformFn, obj) => Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, transformFn(value)])
)
const apiCall = {"persons":{"de":{"name":"Bob","scale":100,"value":2459},"be":{"name":"Alice","scale":10000,"value":1459}}}
const result = mapValues(
val => mapValues(v => ({
...v,
result: v.value / v.scale,
}), val),
apiCall
)
console.log(result)
If you have multiple nested levels with properties you don't want to transform, we can also pass the key to the transformFn for a more granular change. Now we can create a recursive function to traverse the tree, and only update objects which have a specific key.
const mapValues = (transformFn, obj) => Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, transformFn(value, key)])
)
const fn = obj => mapValues(
(val, key) => {
// if the key is data create a new object with a result property
if(key === 'data') return ({
...val,
result: val.value / val.scale,
})
// if it's object pass it to the recursive function
if(typeof val === 'object') return fn(val)
return val
},
obj
)
const apiCall = {"id":"3454353458764389759834534534","json_data":{"persons":{"de":{"name":"Bob","data":{"scale":100,"value":2459}},"be":{"name":"Alice","data":{"scale":10000,"value":1459}}},"url":"https://stackoverflow.com/","timestamp":"2021-10-23T12:00:11+00:00","disclaimer":"Some bla bla"}}
const result = fn(apiCall)
console.log(result)
In Firestore you can update fields in nested objects by a dot notation (https://firebase.google.com/docs/firestore/manage-data/add-data?authuser=0#update_fields_in_nested_objects). I wonder how to make that work in Typescript / Javascript.
For example the following object:
const user = {
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: true
}
},
token: {
custom: 'safghhattgaggsa',
public: 'fsavvsadgga'
}
}
How can I update this object with the following changes:
details.email.verified = false;
token.custom = 'kka';
I already found that Lodash has a set function:
_.set(user, 'details.email.verified', false);
Disadvantage: I have to do this for every change. Is their already a method to update the object with an object (like firestore did)?
const newUser = ANYFUNCTION(user, {
'details.email.verified': false,
'token.custom' = 'kka'
});
// OUTPUT for newUser would be
{
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: false
}
},
token: {
custom: 'kka',
public: 'fsavvsadgga'
}
}
Does anyone know an good solution for this? I already found more solutions if I only want to change one field (Dynamically set property of nested object), but no solution for more than one field with one method
I think you are stuck with using a function but you could write it yourself. No need for a lib:
function set(obj, path, value) {
let parts = path.split(".");
let last = parts.pop();
let lastObj = parts.reduce((acc, cur) => acc[cur], obj);
lastObj[last] = value;
}
set(user, 'details.email.verified', false);
if what you want to do is merge 2 objects then it is a bit trickier:
function forEach(target, fn) {
const keys = Object.keys(target);
let i = -1;
while (++i < keys.length) {
fn(target[keys[i]], keys[i]);
}
}
function setValues(obj, src) {
forEach(src, (value, key) => {
if (value !== null && typeof (value) === "object") {
setValues(obj[key], value);
} else {
obj[key] = value;
}
});
}
let obj1 = {foo: {bar: 1, boo: {zot: null}}};
let obj2 = {foo: {baz: 3, boo: {zot: 5}}};
setValues(obj1, obj2);
console.log(JSON.stringify(obj1));
One solution in combination with lodash _.set method could be:
function setObject(obj, paths) {
for (const p of Object.keys(paths)) {
obj = _.set(obj, p, paths[p]);
}
return obj;
}
I would like to map one array of object into another in a more functional style, I am using typescript.
Basically I am using delete to remove a property on a object, I would like to know if there is a better way to write it.
const data = props.data.map(d => ({
order: d.position,
logs: d.batches.map(b => {
let log= {
amount: b.scrap,
batchNumber: '', // NO GOOD
}
if (!b.batch || b.batch.length === 0) {
delete log.batchNumber // NO GOOD
}
return log
}),
}))
example input data:
const data = [
position: 1,
batches: [
{batchNumber: '', ammount: 3}
]
]
result:
const data = [{
order: 1,
logs:[ {ammount:3}]
}
]
You can do another map on the batches to return a new array of objects, and attach that to your returned object instead:
const out = data.map(({ position: order, batches }) => {
const logs = batches.map(({ batchNumber, ammount }) => {
if (batchNumber) return { batchNumber, ammount };
return { ammount };
});
return { order, logs }
});
DEMO
One approach would be to make a shallow copy of the target omitting keys you want to delete, for example:
let drop = key => obj => Object.keys(obj).reduce((r, k) =>
k === key ? r : {...r, [k]: obj[k]}, {});
let test = [
{foo:11, bar:2, baz: 3},
{foo:22, bar:2, baz: 3},
{foo:33, bar:2, baz: 3},
];
console.log(test.map(drop('bar')));
To add another option to the mix: it is possible to use Object.assign to optionally assign the property:
const data = [{
position: 1,
batches: [{batchNumber: '',ammount: 3}, {batchNumber: 'withNr',ammount: 4}]
}];
const res = data.map(d =>
({
order: d.position,
logs : d.batches.map(({ammount, batchNumber}) => Object.assign({ammount}, batchNumber ? {batchNumber} : null ))
})
);
console.log(res);