Copy object's attributes without having to repeat all the variable names? - javascript

I have an object and I'd like to change some of its attributes' names from mixedCase to snake_case without modifying the original object, and without having to re-write all the variable names twice. This example only includes two variables, but in my real code there are 10+ of them and it feels silly to repeat it all just to get two mixedCase variables into snake_case:
function do_stuff(data) {
const { totalScore, position } = data;
const params = { total_score: totalScore, position };
return do_more_stuff(params);
}

You could use these functions:
function snakeCase(s) {
return s.replace(/([A-Z])/g, c => '_' + c.toLowerCase());
}
function snakeCaseKeys(obj) {
return Object.keys(obj).reduce( (acc, key) =>
Object.assign(acc, { [snakeCase(key)]: obj[key] }), {} );
}
// Sample use:
const params = snakeCaseKeys({totalScore: 1, position: 2});
console.log(params);

Related

Insert element inside array

I have a function
checkName(output) {
output.filter((NewData) => {
return this.props.elements.filter((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name,
//there i need to add another element
// Need to add newData.number = OldData.number
}
return NewData
})
})
return output
}
and I call this function like:
const named = this.checkName(product.rows)
Now I need to add to my product's array that I passed to checkName the value "OldData.Number" to "newData.Number" that is not defined in product (so I need to create this field)
For example:
Product before the checkName function
product.rows = [NewData.name]
Product after the checkName function
product.rows = [NewData.name="value of OldData.name", NewData.number="value of OldData.number"]
How can I obtain this result?
There are 2 confusing things in your code:
You are using filter to execute an action in each member of the output array. However, filter should be used to... well, filter that array, meaning that is should not modify it, just return a sub-set of it. Instead, you might want to use forEach. However, taking into accound the next bullet, probably you want to use map.
You are modifying the array passed to the checkName function. This is confusing and can lead to hard-to-find bugs. Instead, make your function "pure", meaning that it should not mutate its inputs, instead just return the data you need from it.
I would suggest some implementation like this one:
checkName(output){
return output.map((NewData) => {
// find the old data item corresponding to the current NewData
const OldData = this.props.elements.find(x => x.key === NewData.key);
if (OldData) {
// If found, return a clone of the new data with the old data name
// This uses the spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
return {
...NewData, // Clone the NewData object
name: OldData.name, // set the value found in OldData.name in the "name" field of the cloned object
number: OldData.number, // You can do the same for each field for which you want to replace the value cloned from NewValue
};
} else {
// Otherwise, just return a clone of the NewData
return { ...NewData };
}
}
}
The usage would be like this:
const named = this.checkName(product.rows)
Be aware that the product.rows array won't be modified!
You can get keys and values of the old object.
const keys = Object.keys(oldObject);
const values = Object.values(oldObject);
// or
const [keys, values] = Object.entries(oldObject);
After, you will create a loop with all keys of oldObject, and insert in newObject like a array.
keys.forEach( (key, index) => newObject[key] = values[index]);
// or
for (const [key, value] of Object.entries(object1)) {
newObject[key] = value
}
Use map like this.
checkName(output){
return output.map(( NewData) =>{
this.props.elements.forEach((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name;
NewData.number = OldData.number;
}
})
return NewData;
})
// return output;
}

How to override object get fallback

I'm trying to create an object that returns the property name for any property that was accessed. Is this possible to do cleanly in javascript / nodejs? This is what I would like to accomplish:
const mirror = {/*...*/};
console.log(mirror[1]);
// => 1
console.log(mirror.123);
// => '123'
console.log(mirror.randomPropertyHere);
// => 'randomPropertyHere'
I can overwrite a getter for a specific property, but I don't know how to do it generically. Also how can I differentiate between a number and a string?
My (not working) attempts
const mirror = {
get[1] () {
console.log('number');
return 1;
},
get['1'] () {
console.log('string');
return '1';
}
};
console.log(mirror[1]);
console.log(mirror['1']);
Very much appreciate your time and help!
With a proxy.
const mirror = new Proxy({}, {
get(_, prop) { return prop; }
});
console.log(mirror[1]);
// => 1
console.log(mirror['123']);
// => '123'
console.log(mirror.randomPropertyHere);
// => 'randomPropertyHere'
Also how can I differentiate between a number and a string?
Can't, object properties have to be strings or symbols.

Remove an object's key and value using a variable from function

Hey I'm trying to remove a key:value pair from state inside a Javascript Object.
It works when I hardcode the key name in the code, but when I try to use a variable from a function call, it does nothing.
Can somebody help me out?
Here's an object example:
toppingsSelected: {
"Onion":"true",
"Mushrooms":"true",
}
This works, hardcoded:
deleteTopping = toppingName => {
const { Onion, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns object without onion
};
This doesn't work:
deleteTopping = toppingName => {
const toppingName = "Onion"; // Variable gets passed in
const { toppingName, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns original object, no change made
};
So I'm basically trying to remove a key from React state but I'm pretty new to Javascript.
How can I make Javascript aware that toppingName is a key?
Another option is to add square brackets arround toppingName, and assign it to a variable. As #Bergi pointed out in the comments, this option does not mutate toppingsSelected
const toppingsSelected = {
"Onion":"true",
"Mushrooms":"true",
};
const toppingName = "Onion";
const {
[toppingName]: topping,
...withoutOnion
} = toppingsSelected;
console.log(JSON.stringify(withoutOnion));
To set the React state, you'd then do this
this.setState({ toppingsSelected: withoutOnion })
You can use delete e.g.
delete toppingsSelected[toppingName];
One way of doing this is using Array.prototype.filter()
const _obj = {
'Onion': true,
'notOnion': false
};
const newObj = Object.keys(_obj)
.filter(key => key !== 'Onion')
.reduce((acc, cur) => ({ ...acc, cur }), {})
console.log(newObj); // { notOnion: false }
This will return a new object without the 'Onion' property

Shortest way to set value to variable in Knockout

In Knockout I have observable variable location. It is of type LocationEdit. This viewModel has observable and not fields.
I have collection of field names : fields. For each field I want to reset values for location
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
if (ko.isObservable(this.location()[field])) {
this.location()[field](locs[0][field]);
} else {
this.location()[field] = locs[0][field];
}
}
});
To make this code more simpler (remove if-clauses), Can I somehow set value to this.location()[field] in one line?
You could use the conditional operator (... ? ... : ... ;) although it doesn't change much:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
ko.isObservable(this.location()[field]) ? this.location()[field](locs[0][field]) : this.location()[field] = locs[0][field];
}
});
Or you could write a function:
function upd(arr, index, val) {
ko.isObservable(arr[index]) ? arr[index](val) : arr[index] = val;
}
Usage:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
upd(this.location(), field, locs[0][field]);
}
});
See demo.
You could even add this function to ko:
if(typeof ko.updatePotentialObservable == 'undefined')
ko.updatePotentialObservable = function (arr[index], val) {
ko.isObservable(obj) ? arr[index](val) : arr[index]= val;
}
Usage:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
ko.updatePotentialObservable(this.location(), field, locs[0][field]);
}
});
See other demo
To be honest, I think Gôtô's answers are definitely your best options. Basically, you'd want to create a utility function similar to ko.unwrap but setting a value.
But since you said "also want to find another solution", here's a different utility function. I think the most confusing part of your code is the returning calls to locs[0][field] and this.location()[field]. I'd want something with this signature:
reset(source, target, keys);
So, in your code, you could do:
reset(
this.location(),
locs[0],
fields.filter(f => this.uniqueField(locs, f))
);
Now, writing this method, I ended up with this:
const mergePropsObs = (function() {
// Return a method for setting a specific property in object
const getSetter = obj => prop => ko.isObservable(obj[prop])
? obj[prop]
: val => obj[prop] = val;
// Return unique keys for two objects
// (I went with a quick oneliner; there are many ways to do this)
const allKeys = (obj1, obj2) =>
Object.keys(Object.assign({}, obj1, obj2));
return (base, ext, onlyProps) => {
const props = onlyProps || allKeys(base, ext);
const values = props.map(p => ko.unwrap(ext[p]));
props
.map(getSetter(base))
.forEach((setter, i) => setter(values[i]));
};
}());
var base = { a: 1, b: ko.observable(2), c: 5 };
mergePropsObs(
base,
{ a: 2, b: 3 },
["a", "b"]);
console.log(base.a);
console.log(base.b());
console.log(base.c);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
This utility method can be used with the signature mentioned above. It also has a fallback for when you don't provide an array of field names.

Transform all keys in array from underscore to camel case in js

So, I need to transform all keys in array from underscore to camel space in js. That is what I need to do before send form to server. I'm using Angular.js and I want to represent it as a filter (but I think it's not rly important in this case). Anyway, here is a function I've created.
.filter('underscoreToCamelKeys', function () {
return function (data) {
var tmp = [];
function keyReverse(array) {
angular.forEach(array, function (value, key) {
tmp[value] = underscoreToCamelcase(key);
});
return tmp;
}
var new_obj = {};
for (var prop in keyReverse(data)) {
if(tmp.hasOwnProperty(prop)) {
new_obj[tmp[prop]] = prop;
}
}
return new_obj;
};
function underscoreToCamelcase (string) {
return string.replace(/(\_\w)/g, function(m){
return m[1].toUpperCase();
});
}
})
Here I will try to explain how it works, because it looks terrible at first.
underscoreToCamelcase function just reverting any string in underscore to came case, except first character (like this some_string => someString)
So, as I said earlier, I should revert all keys to camel case, but as you understant we can't simply write
date[key] = underscoreToCamelcase(key)
so keyReverse function returns a reverted array, here is example
some_key => value
will be
value => someKey
and for the last I simply reverting keys and values back, to get this
someKey => value
But, as you already may understand, I got a problem, if in array exists the same values, those data will be dissapear
array
some_key1 => value,
some_key2 => value
returns as
someKey2 => value
So how can I fix that? I have a suggestion to check if those value exists and if it is add some special substring, like this
some_key1 => value,
some_key2 => value
value => someKey1,
zx99value => someKey2
and after all parse it for zx99, but it I think I`m going crazy...
Maybe some one have a better solution in this case?
Important! Them main problem is not just to transform some string to camel case, but do it with array keys!
If you use an existing library to do the camelCase transform, you can then reduce an object like so
import {camelCase} from 'lodash/string'
const camelCaseKeys = (obj) =>
Object.keys(obj).reduce((ccObj, field) => ({
...ccObj,
[camelCase(field)]: obj[field]
}), {})
.filter('underscoreToCamelKeys', function () {
return function (data) {
var tmp = {};
angular.forEach(data, function (value, key) {
var tmpvalue = underscoreToCamelcase(key);
tmp[tmpvalue] = value;
});
return tmp;
};
function underscoreToCamelcase (string) {
return string.replace(/(\_\w)/g, function(m){
return m[1].toUpperCase();
});
}
})
thanks to ryanlutgen
As an alternative solution, you could use the optional replacer parameter of the JSON.stringify method.
var result = JSON.stringify(myVal, function (key, value) {
if (value && typeof value === 'object') {
var replacement = {};
for (var k in value) {
if (Object.hasOwnProperty.call(value, k)) {
replacement[underscoreToCamelcase(k)] = value[k];
}
}
return replacement;
}
return value;
});
Of course you'll end up with a string and have to call JSON.parse to get the object.

Categories

Resources