I have a .json file like this:
{
"width": 700,
"height": 1382,
"dataID": {
"id1": "54321",
"id2": "12345"
}
}
I need to get value of id1 or id2 keys dynamically (using a variable). I use Cypress function cy.read() and by using definite strings it works good:
cy.readFile(pathToConfigFile).then(($file) => {
const id1value = $file.dataID.id1;
});
But how to wrap this expression into variable containing id1?
There is a similar question : Dynamically access object property using variable
However the solution proposed there refers only to the first level of depth. With square brackets I can get the following values:
cy.readFile(pathToConfigFile).then(($file) => {
const customVariable = "dataID";
const id1value = $file[customVariable];
});
But in case of it returns id1value = undefined:
cy.readFile(pathToConfigFile).then(($file) => {
const customVariable = "dataID";
const id1 = "id1";
const id1value = $file[customVariable][id1];
});
You will need to check the value of the variable after the first check so you know whether you can read the second-level value.
cy.readFile(pathToConfigFile).then(($file) => {
const customVariable = "dataID";
const id1 = "id1";
const idValues = $file[customVariable];
return idValues ? idValues[id1] : undefined;
});
Instead of undefined you can return some default value if you prefer.
There are also packages which can be used to do this automatically for you, such as lodash's _.get() method.
As a beginner still in JS, I have to crack someone else's code that is not documented, obviously.
At some point this code arrives to the function that is supposed to build an object.
let obj = {
catName:"cat",
catAge: someAge,
catLength: "one half of a meter no one cares how many cm",
...
}
This object is then sent to another function that is assigning keys to the table names to print out this object in .csv format against column names (to be able to open it in Excel for example)
The datamodel is saved in a separate JS file.
function that would parse to csv format uses this:
const pathtoFile = `////directories/cat.csv`;
const globalParser = new Parser({
header: !fs.existsSync(pathtoFile),
delimiter: ";",
excelStrings: false,
fields: ReportFields, //here is where the object data model comes from
});
const data = globalParser.parse(**catOBJ**) + "\n"; //catObj here is exactly what arrives and what is to be parsed
if (!fs.existsSync(pathtoFile)) {
fs.mkdirpSync(tmpcatReportsPath);
fs.writeFileSync(pathtoFile, data, "utf-8");
} else {
fs.appendFileSync(pathtoFile, data, "utf-8");
}
Problem:
I need to make sure that even if I do not know some of the values for some keys( ex: catLength), I can still assure that the object will get to .csv format and instead of this column name I will simply have ""
Questions:
Does it look more reasonable to you that I first fetch this template object , create an instance of it and then assign the parameters I know to it and then send it to the function?
if not, how do you proceed to make sure that the values that are not known are positioned as "" if I do not have a clue what these key-names are?
how do you proceed to make sure that the values that are not known are positioned as "" if I do not have a clue what these key-names are?
1) To obtain the keys of an object you should ouse the Object.keys() function, which returns an array with keys like ['catName', 'catAge', 'catLength', ...].
2) Once you have the keys of the object, you should map each of them and check if there is a null value:
let keys = Object.keys(obj); // Get array of keys
let result = {};
// Iterate all keys
keys.forEach(key => {
result[key] = (obj[key] == null ? "" : obj[key]);
});
Notice that using obj[key] == null will also catch undefined values.
You can try the code in the snippet:
let obj = {
catName: "cat",
catAge: 5,
catLength: "one half of a meter no one cares how many cm",
catColor: null // Need to catch this
};
let keys = Object.keys(obj);
let result = {};
keys.forEach(key => {
result[key] = (obj[key] == null ? "" : obj[key]);
});
console.log(result);
I have the following object with (always) 2 properties. It wil always have 2 properties. 3 properties won't be possible:
var people = {
'John': { ... },
'Peter': { ... }
}
And I have the variable var name = 'John'.
Is there a simple way to get the value of property 'Peter' when the value of name is 'John'?
Without hard-coding it with the names John en Peter. So the functionality must get the opposite property of the value in variable name
let name = 'John'; // or whatever
let names = Object.keys(people);
let otherName = names.find(n => n !== name);
people[otherName] // this gives you the value of the other name's property
Object.keys will give you an array of the property names.
filter lets you filter that array.
So:
const name = "John";
const people = {
'John': 1,
'Peter': 1
};
const [result] = Object.keys(people).filter(person => person !== name);
console.log({
result
});
I wrote a simple function, that does this. It takes a key, and an object. It returns the value of the element in the given object by the inverse key of the given key. This will only work if the object only has two keys.
var people = {
'John': { 'b':[3,4] },
'Peter': { 'a':[1,2] }
}
getInverseObjectElem = (key,object) => { // Define function 'getInverseObjectElem'
listOfObjectKeys = Object.keys(object) // Create an array of the object keys
inverseKey = listOfObjectKeys.filter(k => k != key)[0] // filter the list of keys, and get the first key that doesnt match the given key.
return object[inverseKey] // Return the value in the object, by the inverseKey
}
console.log(getInverseObjectElem('John',people))
You could try this:
for (let key in people){
if (key != name) return people[key];
}
My javascript object looks like this:
const someObj = {
arr1: ["str1", "str2"],
arr2: ["str3", "str4"]
}
In attempting to rename a key (e.g. arr1), I end up deleting the existing key and writing a new key with the original value. The order of obj changes.
someObj = {
arr2: ["str3", "str4"],
renamedarr1: ["str1", "str2"]
}
How do I rename a key while preserving the key order?
In the end it was solved in a js-vanila way rather than a react way.
In case somebody would look for a similar solution, I am posting the code I ended up using. Inspired by Luke's idea:
const renameObjKey = ({oldObj, oldKey, newKey}) => {
const keys = Object.keys(oldObj);
const newObj = keys.reduce((acc, val)=>{
if(val === oldKey){
acc[newKey] = oldObj[oldKey];
}
else {
acc[val] = oldObj[val];
}
return acc;
}, {});
return newObj;
};
You might want to consider reducing the array of keys into a new object.
To do this, you need also to know which key changed to what.
Reduce the array of keys
use a reducer which checks for a key change, and change it if necessary.
add the key to the object with the value
After that you have a Object with the order you had before, and possibly a changed key is still at the same position
Something like this might work (not tested)
const changedKeyMap = {"previousKey": "newKey"};
const keys = Object.keys(this.state.obj);
const content = e.target.value;
const result = keys.reduce((acc, val) => {
// modify key, if necessary
if (!!changedKeyMap[val]) {
val = changedKeyMap[val];
}
acc[val] = content;
// or acc[val] = this.state.obj[val] ?
return acc;
}, {});
As you can see, you need to keep track of how you changed a key (changedKeyMap).
The reduce function just iterates over all keys in correct order and adds them to a newly created object. if a key is changed, you can check it in the changedKeyMap and replace it. It will still be at the correct position
I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).