I have the following JSON object
[
{
'parameter-name': 'device',
enabled: true,
value: '077743322L102515',
description: 'device identifier. Should be used only when multiple devices are connected at once'
},
{
'parameter-name': 'app_id',
enabled: true,
value: 'com.instagram.andrpbj',
description: ' when using this parameter, you are able to use Insomniac on a cloned Instagram-application. Just provide the new package name'
},
{
'parameter-name': 'old',
enabled: false,
value: 'True',
description: 'add this flag to use an old version of uiautomator. Use it only if you experience problems with the default version'
}
]
i wanted to access the value of the parameter-name: 'old', by value i mean value
is there a one step solution to do that without iterating through each entry ?
From my point of view, using Array.find() is the cleanest solution to get the value:
const { value } = data.find((obj) => obj['parameter-name'] === 'old');
If your goal is to edit the value as you're asking in the comment, you can get the index of the targeted object within the array using Array.findIndex() and then edit the data:
const objIdx = data.findIndex((obj) => obj['parameter-name'] === 'old');
data[objIdx].value = 'newValue'
Instead of getting the index, you could even manipulate the value on the object directly:
const obj = data.find((obj) => obj['parameter-name'] === 'old');
obj.value = 'newValue';
Code Snippet
const data = [{
'parameter-name': 'device',
enabled: true,
value: '077743322L102515',
description: 'device identifier. Should be used only when multiple devices are connected at once'
},
{
'parameter-name': 'app_id',
enabled: true,
value: 'com.instagram.andrpbj',
description: ' when using this parameter, you are able to use Insomniac on a cloned Instagram-application. Just provide the new package name'
},
{
'parameter-name': 'old',
enabled: false,
value: 'True',
description: 'add this flag to use an old version of uiautomator. Use it only if you experience problems with the default version'
}
];
const obj = data.find((obj) => obj['parameter-name'] === 'old');
obj.value = 'newValue';
console.log(data);
In order to account for a situation where the 'searched-value' is not found, it may be better to split the assignment like this:
const foundObj = data?.find((obj) => obj['parameter-name'] === 'old');
if (foundObj) foundObj.value = 'newValue';
const data = [{
'parameter-name': 'device',
enabled: true,
value: '077743322L102515',
description: 'device identifier. Should be used only when multiple devices are connected at once'
},
{
'parameter-name': 'app_id',
enabled: true,
value: 'com.instagram.andrpbj',
description: ' when using this parameter, you are able to use Insomniac on a cloned Instagram-application. Just provide the new package name'
},
{
'parameter-name': 'old',
enabled: false,
value: 'True',
description: 'add this flag to use an old version of uiautomator. Use it only if you experience problems with the default version'
}
];
const foundObj = data?.find((obj) => obj['parameter-name'] === 'old');
if (foundObj) foundObj.value = 'newValue';
console.log(data);
An alternative to Alexander's answer if you don't want to mutate the array object directly.
It's a very generic function that accepts a query object (key, oldValue, newValue) so it will work on all the property keys, not just parameter-name.
It finds the object that matches the query criteria (if nothing is found the function returns null), filters out the objects that don't match the query criteria, and then returns that filtered array with a new updated object.
const arr=[{"parameter-name":"device",enabled:!0,value:"077743322L102515",description:"device identifier. Should be used only when multiple devices are connected at once"},{"parameter-name":"app_id",enabled:!0,value:"com.instagram.andrpbj",description:" when using this parameter, you are able to use Insomniac on a cloned Instagram-application. Just provide the new package name"},{"parameter-name":"old",enabled:!1,value:"True",description:"add this flag to use an old version of uiautomator. Use it only if you experience problems with the default version"}];
// Accept an array, and a query object
function change(arr, query) {
// Destructure the query
const { key, oldValue, newValue } = query;
// Look for the object where the value
// of the key specified in the query
// matches the oldValue
const found = arr.find(obj => {
return obj[key] === oldValue;
});
// If there isn't a match return null
if (!found) return null;
// Otherwise `filter` out the objects that
// don't match the criteria...
const filtered = arr.filter(obj => {
return obj[key] !== oldValue;
});
// Destructure all the object properties
// away from the property we want to update
const { [key]: temp, ...rest } = found;
// Return a new array with a new updated object
return [ ...filtered, { [key]: newValue, ...rest } ];
}
const query = {
key: 'parameter-name',
oldValue: 'old',
newValue: 'new'
};
console.log(change(arr, query));
const query2 = {
key: 'value',
oldValue: '077743322L102515',
newValue: '999999999'
};
console.log(change(arr, query2));
data.filter((value) => {
if(value['parameter-name'] === 'old'){
console.log(value);
}
})
Related
Hi I have an array of objects which string starts with a specific prefix and if that key value is true then remove all the objects in an array that contains the same key(prefix)
below is the array of object:
const data = [{
field_name_key: "Recive_IsViaEmail",
fieldValue: false
},
{
field_name_key: "Recive_IsViaSMS",
fieldValue: false
},
{
field_name_key: "Sender_IsViaEmail",
fieldValue: false
},
{
field_name_key: "Sender_IsViaSMS",
fieldValue: true
},
]
here "Sender_IsViaSMS" contains true hence remove all the objects that start with the prefix key Sender_IsVia
Final result is this:
const data = [{
field_name_key: "Recive_IsViaEmail",
fieldValue: false
},
{
field_name_key: "Recive_IsViaSMS",
fieldValue: false
}
]
An inefficient but short solution would be to use Array.filter and set the condition of the callback to whether data contains an item with the same field_name_key property prefix and whose fieldValue property is true:
const data=[{field_name_key:"Recive_IsViaEmail",fieldValue:false},{field_name_key:"Recive_IsViaSMS",fieldValue:false},{field_name_key:"Sender_IsViaEmail",fieldValue:false},{field_name_key:"Sender_IsViaSMS",fieldValue:true}];
const res = data.filter(e => !data.find(f => f.field_name_key.split("IsVia")[0] == e.field_name_key.split("IsVia")[0] && f.fieldValue))
console.log(res)
Build up a list of the prefixes to remove first, then just run a filter.
I am not sure on how you are coming up with the prefix of a given field name however. Given your example you're expecting "Sender_IsViaSMS" to also purge "Recive_IsViaSMS". But you should be able to take this and adapt it as you see fit.
const data=[{field_name_key:"Recive_IsViaEmail",fieldValue:false},{field_name_key:"Recive_IsViaSMS",fieldValue:false},{field_name_key:"Sender_IsViaEmail",fieldValue:false},{field_name_key:"Sender_IsViaSMS",fieldValue:true}];
const prefixsToAxe = new Set();
// Having trouble with this part in your question, so ran with this, replace as you see fit
const determinePrefix = (field_name_key) => field_name_key.split('IsVia')[0];
for (const entryToAxe of data.filter(x => x.fieldValue === true)) {
const prefixToAxe = determinePrefix(entryToAxe.field_name_key);
prefixsToAxe.add(prefixToAxe);
}
const purged = data.filter(entry => {
const prefixInQuestion = determinePrefix(entry.field_name_key);
return !prefixsToAxe.has(prefixInQuestion);
})
console.log(purged)
You can create a function that first finds the entries to remove, then remove one by one:
const data = [{
field_name_key: "Recive_IsViaEmail",
fieldValue: false
},
{
field_name_key: "Recive_IsViaSMS",
fieldValue: false
},
{
field_name_key: "Sender_IsViaEmail",
fieldValue: false
},
{
field_name_key: "Sender_IsViaSMS",
fieldValue: true
},
]
const sanitizeData = (list) => {
// find entry keys to remove
const toRemove = list.reduce((prev, el) => {
if(el.fieldValue === true) return el.field_name_key.split('_')[0]
})
// remove
const removed = list.filter(el => {
return !toRemove.includes(el.field_name_key.split('_')[0])
})
return removed
}
console.log(sanitizeData(data))
I'm stuck in mapping object to array.
I use map, but its add every object field in array and i got a lot of undefined.
const mapKey: { [key: string]: number } = {
'hello': 3,
};
preferences = {
hello: true,
.....
.....
}
const array = Object.entries(preferences).map(([key, value]) => {
return mapKey[key] && { index: mapKey[key], visible: true };
});
result is:
[undefined, undefined....{ index: mapKey[key], visible: true }]
but i need just [{ index: mapKey[key], visible: true }]
The Array#map method generates an array based on return value, it's not suited for requirement so use Array#reduce method.
const array = Object.entries(preferences).reduce((arr, [key, value]) => {
// push into the array only if defined
columnIndexMap[key] && arr.push({ index: mapKey[key], visible: true });
// return the array reference for next iteration
return arr;
// set initial value as empty array for the result
}, []);
One-liner solution:
const array = Object.entries(preferences).reduce((arr, [key, value]) => (columnIndexMap[key] && arr.push({ index: mapKey[key], visible: true }), arr), []);
The answer with reduce of course works and is efficient. You can also use filter() + map. This has the disadvantage of looking at values twice, but has a readability advantage. Which is more important, of course, depends on your use-case. I would prefer the following unless there is so much data that the additional work is noticeable:
const mapKey = { 'hello': 3, 'test':4, 'test2': 5};
let preferences = { hello: true, test:false, test2:true}
let filtered = Object.entries(preferences)
.filter(([k, visible]) => visible)
.map(([k, visible]) => ({ index: mapKey[k], visible }))
console.log(filtered)
So my call returns something like:
data:
{
nameData: 'Test33333',
emailData: email#email.com,
urlLink: link.com
additionalDetails: [
{
field: 'email',
value: 'other#email.com'
},
{
field: 'name',
value: 'name1223'
}
]
}
Now, I want to make a function that would take the passed parameter (data) and make an array of objects, that should look like below. It should be done in more generic way.
Array output expectation:
fullData = [
{
name: 'data_name'
value: 'Test33333'
},
{
name: 'data_email',
value: 'email#email.com'
},
{
name: 'data_url',
value: 'Link.com'
},
extraData: [
//we never know which one will it return
]
];
It should be done in the function, with name, for example:
generateDataFromObj(data)
so
generateDataArrFromObj = (data) => {
//logic here that will map correctly the data
}
How can this be achieved? I am not really proficient with JavaScript, thanks.
Assuming that you keep your data property keys in camelCase this will work for any data you add, not just the data in the example. Here I've used planetLink. It reduces over the object keys using an initial empty array), extracts the new key name from the existing property key, and concatenates each new object to the returned array.
const data = { nameData: 'Test33333', emailData: 'email#email.com', planetLink: 'Mars' };
function generateDataArrFromObj(data) {
const regex = /([a-z]+)[A-Z]/;
// `reduce` over the object keys
return Object.keys(data).reduce((acc, c) => {
// match against the lowercase part of the key value
// and create the new key name `data_x`
const key = `data_${c.match(regex)[1]}`;
return acc.concat({ name: key, value: data[c] });
}, []);
}
console.log(generateDataArrFromObj(data));
Just run a map over the object keys, this will return an array populated by each item, then in the func map runs over each item, build an object like so:
Object.keys(myObj).map(key => {return {name: key, value: myObj[key]}})
Given an immutable state like this:
alerts: {
5a8c76171bbb57b2950000c4: [
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 1
},
{
_id:5af7c8722552070000000068
device_id:5a8c76171bbb57b2950000c4
count: 2
}
]
}
and an object like this:
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 2
}
I want to replace the object with the same id in the alerts state (immutable), such that end result looks like this:
alerts: {
5a12356ws13tch: [
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 2
},
{
_id:5af7c8722552070000000068
device_id:5a8c76171bbb57b2950000c4
count: 2
}
]
}
How can I do that? With mergeDeep, getIn, setIn, and updateIn, found on List, Map or OrderedMap ?
I tried doing something like this.. where index is 0 and deviceId is 5a12356ws13tch
Does not work though.
export const oneAlertFetched = (state, {deviceId, index, alert}) => state.setIn(['alerts', deviceId, index], alert).merge({fetching: false})
I tried this as well. Does not work.
export const oneAlertFetched = (state, {deviceId, index, alert}) => {
const a = state.alerts[deviceId][index]
state.alerts[deviceId][index] = Object.assign({}, a, alert)
return
}
By immutable, you mean that your property is non-writable.
If you want to modify your object in-place (not recommended), you will need the property to be at least configurable:
const device = alerts['5a12356ws13tch'][0];
if (Object.getOwnPropertyDescriptor(device, 'count').configurable) {
// Manually make it `writable`
Object.defineProperty(device, 'count', {
writable: true
});
// Update property's value
device.count++;
// Set it back to `non-writable`
Object.defineProperty(device, 'count', {
writable: false
});
}
console.log(device.count); // 2
If it is not configurable (cannot make it writable), or you do not want to jeopardize your application (it must be non-writable on purpose), then you should work on copies.
const device = alerts['5a12356ws13tch'][0];
alerts['5a12356ws13tch'][0] = Object.assign({}, device, {count: device.count + 1});
Object.assign() works on flat objects. If you need deep copy, have a look at my SO answer there.
I think you mean you want to return a new object with the updated payload?
function getNextAlerts(alerts, parentDeviceId, payload) {
const alertsForDevice = alerts[parentDeviceId];
if (!alertsForDevice || alertsForDevice.length === 0) {
console.log('No alerts for device', deviceId);
return;
}
return {
...alerts,
[parentDeviceId]: alerts[parentDeviceId].map(item =>
item._id === payload._id ? payload : item
),
}
}
const alerts = {
'5a12356ws13tch': [
{
_id: '5af7c8652552070000000064',
device_id: '5a8c76171bbb57b2950000c4',
count: 1
},
{
_id: '5af7c8722552070000000068',
device_id: '5a8c76171bbb57b2950000c4',
count: 2
}
]
};
const nextAlerts = getNextAlerts(alerts, '5a12356ws13tch', {
_id: '5af7c8652552070000000064',
device_id: '5a8c76171bbb57b2950000c4',
count: 2,
});
console.log('nextAlerts:', nextAlerts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
If you're working with plain JavaScript objects and want to keep "immutable" approach you have to use spreads all over the nested structure of state object.
But, there are some tools already targeting this issue - lenses.
Here is the example of both approaches, array/object spreads and lenses - ramda repl.
In short, your example via spreads:
const oneAlertFetched = (state, { deviceId, index, alert }) => ({
...state,
alerts: {
...state.alerts,
[deviceId]: [
...state.alerts[deviceId].slice(0, index),
{ ...state.alerts[deviceId][index], ...alert },
...state.alerts[deviceId].slice(index + 1)
],
}
})
And via lenses using Ramda's over, lensPath, merge and __*:
const oneAlertFetched = (state, { deviceId, index, alert }) =>
R.over(
R.lensPath(['alerts', deviceId, index]),
R.merge(R.__, alert),
state
)
* R.__ placeholder used to swap 1st & 2nd parameters of R.merge
PS: lenses solution is intentionally adjusted to match the declaration of your function, so you can easily compare two approaches. However, in real life, with such powerful and flexible tool, we can rewrite the function to be more readable, reusable, and performant.
I'm using Redux, React and Lodash with a fairly standard normalized entities store.
When I merge in new entities in a redux reducer, the references to all my existing entities change (despite not being modified), causing any pure components to re-render.
Is there an alternative to lodash's merge that can merge whilst maintaining the existing references to values that are not in the object being merged in?
let entities = {
[1]: {a: true },
[2]: {a: true, b: true },
}
let response = {
[2]: {a: false }
}
let newEntities = _.merge({}, entities, response)
console.log(entities[1] === newEntities[1]) // false
I can't use Object.assign/ES6 Spread here as then newEntities[2].b will be deleted.
I do realise there are alternative solutions such as custom sCU and reselect, however it would be much cleaner to take care of this at the reducer level rather than having to modify every single component that does an equality reference check on its props.
Use mergeWith with a customizer:
let keepRef = (objValue, srcValue) => (
objValue === undefined ? srcValue : _.mergeWith({}, objValue, srcValue, keepRef)
)
let newEntities = _.mergeWith({}, entities, response, keepRef)
I expanded on #Pavlo's awesome answer. I added support for arrays, and collections. I define a collection as an array of object's, where each object has an id key. This is very common in react/redux and normalized data.
import { mergeWith, isPlainObject, isEmpty, keyBy } from 'lodash'
// https://stackoverflow.com/a/49437903/1828637
// mergeWith customizer.
// by default mergeWith keeps refs to everything,
// this customizer makes it so that ref is only kept if unchanged
// and a shallow copy is made if changed. this shallow copy continues deeply.
// supports arrays of collections (by id).
function keepUnchangedRefsOnly(objValue, srcValue) {
if (objValue === undefined) { // do i need this?
return srcValue;
} else if (srcValue === undefined) { // do i need this?
return objValue;
} else if (isPlainObject(objValue)) {
return mergeWith({}, objValue, srcValue, keepUnchangedRefsOnly);
} else if (Array.isArray(objValue)) {
if (isEmpty(objValue) && !isEmpty(srcValue))return [...srcValue];
else if (!isEmpty(objValue) && isEmpty(srcValue)) return objValue;
else if (isEmpty(objValue) && isEmpty(srcValue)) return objValue; // both empty
else {
// if array is array of objects, then assume each object has id, and merge based on id
// so create new array, based objValue. id should match in each spot
if (isPlainObject(objValue[0]) && objValue[0].hasOwnProperty('id')) {
const srcCollection = keyBy(srcValue, 'id');
const aligned = objValue.map(el => {
const { id } = el;
if (srcCollection.hasOwnProperty(id)) {
const srcEl = srcCollection[id];
delete srcCollection[id];
return mergeWith({}, el, srcEl, keepUnchangedRefsOnly);
} else {
return el;
}
});
aligned.push(...Object.values(srcCollection));
return aligned;
} else {
return [ ...objValue, ...srcValue ];
}
}
}
}
Usage:
const state = {
chars: ['a', 'b'],
messages: [
{
id: 1,
text: 'one'
},
{
id: 2,
text: 'ref to this entry will be unchanged'
}
]
}
const response = {
chars: ['c', 'd'],
messages: [
{
id: 1,
text: 'changed ref text one'
},
{
id: 3,
text: 'three'
}
]
}
const stateNext = mergeWith({}, state, response, keepUnchangedRefsOnly)
Resulting stateNext is:
{
chars: [
'a',
'b',
'c',
'd'
],
messages: [
{
id: 1,
text: 'changed ref text one'
},
{
'id': 2,
text: 'ref to this entry will be unchanged'
},
{
'id': 3,
text: 'three'
}
]
}
If you want to keep undefined values, then replace mergeWith in customizer and your use case with assignWith. Example - https://stackoverflow.com/a/49455981/1828637