ES6 spread element - default value - javascript

I need to set an empty object as a default value if the array I'm passing in is empty. Something like:
var obj = { documents: [...question.documents] || [{}] }
I fixed it using a condition, but I want to know if there is a better way to achieve that.
if(obj.documents.length === 0) obj.documents.push({})

Since even empty arrays are truthy, I don't think there's any great elegant solution other than putting an explicit test in there somewhere. Ternaries are more terse than if statements, though:
const question = { documents: [] };
const { documents } = question;
const obj = { documents: documents.length !== 0 ? documents : [{}]}
console.log(JSON.stringify(obj));
Here's another possibility:
const question = { documents: [] };
const [firstElm = {}, ...otherElms] = question.documents;
const obj = { documents: [firstElm, ...otherElms] };
console.log(obj);

There are a couple of ways to write this in a single expression
Using the ternary operator:
var obj = { documents: [
...question.documents.length
? question.documents
: [{}]
]
};
Using a default value
var obj = { documents: [question.documents[0] || {}, ...question.documents.slice(1)] };
In both cases there's some awkwardness stemming from having to refer to the source multiple times

The spread operator is used inside an empty array. I don't see the
point in using the spread operator here. The objective can be achieved
by using the following.
var obj = { documents: question.documents.length ? question.documents : [{}]}
If the method you have provided is being used, you don't need an or clause, because an empty array also returns a truthy value. So it can be written as the following :-
var obj = { documents: question.documents }
if(!obj.documents.length) obj.documents.push({})

this should suit...
const question = {
documents: [],
};
const obj = {
documents: [].concat(question.documents.length ? question.documents : {})
};
console.log(obj);

The shortest way
const obj1 ={...(true&& {x:1})};
console.log(obj1)
const obj2 ={...(false&& {y:1})};
console.log(obj2)

Related

How to conditionally create object property in one line? [duplicate]

This question already has answers here:
In JavaScript, how to conditionally add a member to an object?
(29 answers)
Conditionally set an object property
(7 answers)
How to conditionally add properties to a javascript object literal
(8 answers)
Closed 8 months ago.
I want to create a property on an object conditionally.
The idea is to ensure the property doesn't exist (so not just null) if it has no value.
What I'm doing right now:
// comes from users
req.query = {
foo: true
}
// my object which will contains allowed properties IF EXISTS
const where = {}
if (req.query.foo) {
where.foo = req.query.foo
}
if (req.query.bar) {
where.bar = req.query.bar
}
console.log(where)
It's really boring to repeat this for every different property...
Is there a way to do it in one line?
EDIT:
I don't want to get all properties from req.query
Create object property obj.foo if value exists (or any other condition)
&& operator returns second element if the first boolean condition is fulfilled
es6 spread operator ... does what it does)
const value = 'bar'
const empty = undefined
const obj = {
...value && { foo: value }, // read ...(value && { foo: value }) parenthesis are not necessary
...empty && { empty },
...(100 > 0) && { condition: 'true' },
...(100 < 0) && { condition: 'false' },
}
console.log(obj)
You could do:
const merged = {...where, ...req.query};
Try this:
const elements = ["foo", "bar"];
elements.forEach(el => {
if (req.query[el]) {
where[el] = req.query[el];
}
});
You can simply write it in one line like this :
let where = {};
if (req.query.foo) where = {...where, foo : req.query.foo };
if (req.query.bar) where = {...where, bar : req.query.bar };
I hope that it helps.
If you want a simple one liner (maybe less readable), you can do:
Object.keys(req.query).forEach(key => req.query[key] && (where[key] = req.query[key]));
If you're okay adding a pluck function, you could do it like this:
const req = { query: { baz: '1', foo: '3' } };
const where = {};
const pluck = (keys, obj) =>
keys.reduce((a, k) => typeof obj[k] === 'undefined' ? a : Object.assign(a, { [k]: obj[k] }), {});
const add_foo_bar = (where, query) =>
Object.assign({}, where, pluck(['foo', 'bar'], query));
const res = add_foo_bar(where, req.query);
console.log(res);

Single line subobject check

I'm working with two objects in JS and I need to know if a given subobject exists. My objects look something like this:
obj1 = {
sobj1: 1234,
sobj2: {
ssobj1: "abc",
ssobj2: 1234
}
}
The catch is that I don't know beforehand the exact shape of the object and when I try to check if ssojb1 exists with an if (obj1.sobj2.ssobj1) and sobj2 isn't set, I'll get an error like "trying to read property ssobj1 of undefined.
To circumvent this, my approach was to use a cascade of if statements, but I'm pretty sure that there's a better option. This can get ugly pretty quickly.
Any suggestions?
There is an optional chaining operator supported in modern browsers ?., but you may still be working with if statements at that point. In these cases, utilities may help you.
const isDefined = x => typeof x !== 'undefined'
const { get } = rubico
const pathsToCheck = [
'sobj2.ssobj1',
'sobj2.ssobj3',
]
const checkPath = (path, obj) => isDefined(get(path)(obj))
const obj1 = {
sobj1: 1234,
sobj2: {
ssobj1: "abc",
ssobj2: 1234
}
}
for (const path of pathsToCheck) {
console.log(path, checkPath(path, obj1))
}
<script src="https://unpkg.com/rubico"></script>
Disclaimer: I am the author of rubico.
Documentation for get
Thanks to mousetail for the solution.
Appearently using if ('key' in obj) does the trick, even if obj is undefined.
So:
if ('ssobj1' in obj1) { // true or false
do stuff...
}

Create object by grouping camelCase properties

Recently I've found that I have had to create a object from attributes on a HTML tag. I am doing this in a AngularJS environment, so hyphenated attributes are converted to camelCase, but I could also do the same using data- attributes and dataset
So for example I have:
<element person-name="Grant" animation-jump="123" />
Which gives the object
{
"personName" : "Grant",
"animationJump" : "123"
{
My problem is that I then want to convert that camelCase object into a structured object:
{
"person" : {
"name" : "Grant" },
"animation" : {
"jump" : "123" }
}
I've created a JSFiddle of my QUint Unit Test https://jsfiddle.net/gdt3bonw/
It's actually working for the case I want which is only 1 level, but I would like to get it working for any number of levels because I foresee that it will be needed and so I can release the code publicly.
We will loop through the keys of the object using reduce, building up the result. We decompose each key into its components, such as personName into person and name. We loop over these components, creating subobjects if they do not already exist. Finally, we add the final component to the innermost subobject as a property with the value in question.
Object.keys(input).reduce((result, key) => {
var parts = key.match( /(^|[A-Z])[a-z]+/g) . map(part => part.toLowerCase());
var leaf = parts.pop();
var obj = result;
parts.forEach(part => obj = obj[part] = obj[part] || {});
obj[leaf] = input[key];
return result;
}, {});
You can't use that in this way, and I don't think that it would be a logic proposal. Below I explain why it wouldn't.
obj[["animation","jump"]] = "123"
replace it with
obj["animation"]["jump"] = "123"
and it's all fine.
Why I don't support your idea?
It's messy to use, there is no style in doing that.
There is no logic in using an array as an object key
There is another way of calling an object item by key: using a dot, and that won't support your idea. I think everyone can imagine why.
Why do you need to convert the attribute to camelCase in the first place..? Just do
function arrayToStructuredObject(obj,props){
if (props.length){
obj[props[0]] = props.length > 1 ? {} : value;
arrayToStructuredObject(obj[props.shift()],props);
}
return obj;
}
var props = "animation-jump-tremendous-pinky".split("-"),
value = "123",
obj = {},
sobj = {};
sobj = arrayToStructuredObject(obj, props);
Besides i would like to remind that using the bracket notation to create a property is only possible if the reference that the bracket notation is used upon is predefined as an object. Such as
var o1; // <- undefined
o1["myProp"] = 1; // Uncaught TypeError: Cannot set property 'myProp' of undefined
while
var o2 = {}; // Object {}
o2["myProp"] = 1; // <- 1
then again
o2["myProp"]["myOtherProp"] = 2; // <- 2 but won't type coerce o2.myProp to Object
So speaking of proposals, i am not sure if utilizing bracket notation directly over undefined variables yet as another object creation pattern makes sense or not.
Well in any case one complete solution would be
var inp = {"personName" : "Grant", "animationJump" : "123", "fancyGirlTakesARide" : "987"},
result = Object.keys(inp).reduce(function(p,c,i){
var props = c.replace(/[A-Z]/g, m => "-" + m.toLowerCase()).split("-");
return arrayToStructuredObject(p,props,inp[c])
},{});
function arrayToStructuredObject(obj,props,val){
if (props.length){
obj[props[0]] = props.length > 1 ? {} : val;
arrayToStructuredObject(obj[props.shift()],props,val);
}
return obj;
}
Though I loved the method of splitting the camelCase props by a look-ahead (/?=[A-Z]/) it takes an extra job of lower casing the whole array of prop strings regardless they are already lowercase or not. So i guess this might be slightly faster. (..or not due to the recursive nature of it)
This is not the best solution, but you can actually use arrays as key, in this particular situation, by converting them to a string:
obj[["animation","Jump"].join()] = "123";
This will work with your original object.
A solution, which uses Regex to split camel case string.
var obj = { "animationsJump": "123", "animationsRun": "456", "animationsHide": "789", "personName": "Grant", "personPetsDog": "Snowy", "personPetsCat": "Snowball" },
newObject = {};
Object.keys(obj).forEach(function (k) {
var path = k.split(/(?=[A-Z])/).map(function (s) {
return s.toLowerCase();
}),
last = path.pop();
path.reduce(function (r, a) {
r[a] = r[a] || {};
return r[a];
}, newObject)[last] = obj[k];
});
document.write('<pre>' + JSON.stringify(newObject, 0, 4) + '</pre>');

Is there any way to rename js object keys using underscore.js

I need to convert a js object to another object for passing onto a server post where the names of the keys differ for example
var a = {
name : "Foo",
amount: 55,
reported : false,
...
<snip/>
...
date : "10/01/2001"
}
needs to turn into
a = {
id : "Foo",
total : 55,
updated: false,
...
<snip/>
...
issued : "10/01/2001"
}
where I have lookup obj available for mapping all the keys
var serverKeyMap = {
name : "id",
amount : "total",
reported : "updated",
...
date : "issue"
}
Is there a function available in underscore.js or jQuery that I can use that does this functionality?
thanks
I know you didn't mention lodash and the answers already solve the problem, but someone else might take advantage of an alternative.
As #CookieMonster mentioned in the comments, you can do this with _.mapKeys:
_.mapKeys(a, function(value, key) {
return serverKeyMap[key];
});
And the fiddle: http://jsfiddle.net/cwkwtgr3/
Similar to #pimvdb, you can also do it with a _.reduce:
_.reduce(a, function(result, value, key) {
key = map[key] || key;
result[key] = value;
return result;
}, {});
Fiddle: http://jsfiddle.net/T9Lnr/39/
As far as I know there is no function built into either of these two libraries. You can make your own fairly easily, though: http://jsfiddle.net/T9Lnr/1/.
var b = {};
_.each(a, function(value, key) {
key = map[key] || key;
b[key] = value;
});
You could copy the values to the new properties with standard JavaScript, and remove the original properties with omit, as follows:
a.id = a.name;
a.total = a.amount;
a.updated = a.reported;
a = _.omit(a, 'name', 'amount', 'reported');
// key_map: {old_name1: new_name1, ... }
function rename_keys(object, key_map, is_picked=false){
keys = _.keys(key_map);
new_keys = _.values(key_map);
picked = _.pick(object, keys);
renamed = _.object(new_keys, _.values(picked));
if(is_picked) return renamed;
return _.chain(object).omit(keys).extend(renamed).value();
}
This may be slower than above answers.
No there is no function in either library that explicitly renames keys. Your method is also the fastest (see jsperf tests.) Your best bet, if possible, is to refactor either the client side or server side code so the objects are the same.
I have a transformation operator and would just like to apply it to all keys. I forked pimvdb's fiddle to produce a simple example. In this case it Capitalizes the key. And it dynamically builds the keymap, which I needed to assure works (thanks JSFiddle).
Here is the changed code:
var keymap = {};
_.each(a, function(value, key) {
var oldkey = key;
key = capitalize(key);
keymap[oldkey] = key;
});
_.each(a, function(value, key) {
key = keymap[key] || key;
b[key] = value;
});
Fiddle:
http://jsfiddle.net/mr23/VdNjf/
It's been solved here https://stackoverflow.com/a/30940370/1360897
var keyMapping = {'PropertyA': 'propertyA', ..., 'PropertyF': 'propertyNEW'}
and also a mapping of old and new values, like this
var valueMapping = {'Y': true, 'F': false}
And then using _.map and _.transform, you can transform the object, like this
var result = _.map(allItems, function(currentObject) {
return _.transform(currentObject, function(result, value, key) {
if (key === 'PropertyF' || key === 'PropertyG') {
value = valueMapping(value);
}
result[keyMapping[key]] = value;
});
});
Why don't you use this simple java script ? Value of any key:value pair should be string/number/Boolean.
<script type="text/javascript">
var serverKeyMap = {
name : "id",
amount : "total",
reported : "updated"
};
var a = {
name : "Foo",
amount: 55,
reported : false
};
var b={}; // b is object where you will get your output
for(i in serverKeyMap) b[serverKeyMap[i]]=a[i];
console.log(b); // It gives what you need.
</script>
As user2387823 was saying above 👆 using omit is a great option. For example you could write something like this
function updateObjKey(obj, currentKey, newKey) {
var keyValue = obj[currentKey];
obj = _.omit(obj, [currentKey]);
obj[newKey] = keyValue;
return obj;
}
this ES2015/2017 version 🧙‍♂️
function objectMap(source,keyMap) {
return Object.entries(keyMap).reduce((o,[key , newKey]) => {
o[newKey]=source[key]
return o;},{})
}
const obj = {
name : "Foo",
amount: 55,
reported : false,
date : "10/01/2001"
}
const serverKeyMap = {
name : "id",
amount : "total",
reported : "updated",
date : "issue"
}
const result = objectMap(obj,serverKeyMap);
console.log('🎬 =>' , result);
[Object.entries][1] is es2017 feture will return object key and
value as array
[["name", "id"],["amount", "total"],...]
You really don't need underscore/lodash for this ... nowadays anyways (I realize the question was asked 9 years ago, but this question is (still) ranked highly in search results and I came across it today :-) )
Here's another plain ES2015/2017 version that I like, inspired by #malbarmavi's answer (there's probably a bunch of other plain JS functions out there, but I didn't come across any others in my brief search):
// A general key transform method. Pass it a function that accepts the old key and returns
// the new key.
//
// #example
// obj = transformKeys(obj, (key) => (
// key.replace(/\b(big)\b/g, 'little')
// ))
export function transformKeys(source, f) {
return Object.entries(source).reduce((o, [key, value]) => {
o[f(key) || key] = value
return o
}, {})
}
// Provide an object that maps from old key to new key
export function rekeyObject(source, keyMap) {
transformKeys(source, key => keyMap[key])
}
I referred the lodash documentation ans found mapKeys
https://lodash.com/docs/4.17.15#mapKeys
_.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
return key + value;
});
// => { 'a1': 1, 'b2': 2 }
this perfectly renames the keys and return an object containing the modified desirable object
Using underscore omit and spread operator.
a = _.omit({
...a,
id: a.name,
total: a.amount,
updated: a.reported,
}, ['name', 'amount', 'reported']);
Key assignments below spread operator loads new keys and omit omits the old ones.
You can create your new custom function :
lodash.rename = function(obj, keys, newKeys) {
keys.map((key, index) => {
if(lodash.includes(lodash.keys(obj), key)) {
obj[newKeys[index]] = lodash.clone(obj[key], true);
delete obj[key];
}
});
return obj;
};
Or else if you want to edit only one keyName:
lodash.rename = function(obj, key, newKey) {
if(lodash.includes(lodash.keys(obj), key)) {
obj[newKeys[index]] = lodash.clone(obj[key], true);
delete obj[key];
}
return obj;
};
Using lodash
var obj = _.renameKeys( { 1 : "Geeks",
2 : "Computer_Science_Portal" },
{ 1 : "g", 2 : "c" });
so in your case, you want to apply the serverKeyMap onto object a :
var obj = _.renameKeys(a, serverKeyMap);
from https://www.geeksforgeeks.org/lodash-_-renamekeys-method/

Add new attribute (element) to JSON object using JavaScript

How do I add new attribute (element) to JSON object using JavaScript?
JSON stands for JavaScript Object Notation. A JSON object is really a string that has yet to be turned into the object it represents.
To add a property to an existing object in JS you could do the following.
object["property"] = value;
or
object.property = value;
If you provide some extra info like exactly what you need to do in context you might get a more tailored answer.
var jsonObj = {
members:
{
host: "hostName",
viewers:
{
user1: "value1",
user2: "value2",
user3: "value3"
}
}
}
var i;
for(i=4; i<=8; i++){
var newUser = "user" + i;
var newValue = "value" + i;
jsonObj.members.viewers[newUser] = newValue ;
}
console.log(jsonObj);
A JSON object is simply a javascript object, so with Javascript being a prototype based language, all you have to do is address it using the dot notation.
mything.NewField = 'foo';
With ECMAScript since 2015 you can use Spread Syntax ( …three dots):
let people = { id: 4 ,firstName: 'John'};
people = { ...people, secondName: 'Fogerty'};
It's allow you to add sub objects:
people = { ...people, city: { state: 'California' }};
the result would be:
{
"id": 4,
"firstName": "John",
"secondName": "Forget",
"city": {
"state": "California"
}
}
You also can merge objects:
var mergedObj = { ...obj1, ...obj2 };
thanks for this post. I want to add something that can be useful.
For IE, it is good to use
object["property"] = value;
syntax because some special words in IE can give you an error.
An example:
object.class = 'value';
this fails in IE, because "class" is a special word. I spent several hours with this.
You can also use Object.assign from ECMAScript 2015. It also allows you to add nested attributes at once. E.g.:
const myObject = {};
Object.assign(myObject, {
firstNewAttribute: {
nestedAttribute: 'woohoo!'
}
});
Ps: This will not override the existing object with the assigned attributes. Instead they'll be added. However if you assign a value to an existing attribute then it would be overridden.
extend: function(){
if(arguments.length === 0){ return; }
var x = arguments.length === 1 ? this : arguments[0];
var y;
for(var i = 1, len = arguments.length; i < len; i++) {
y = arguments[i];
for(var key in y){
if(!(y[key] instanceof Function)){
x[key] = y[key];
}
}
};
return x;
}
Extends multiple json objects (ignores functions):
extend({obj: 'hej'}, {obj2: 'helo'}, {obj3: {objinside: 'yes'}});
Will result in a single json object
You can also dynamically add attributes with variables directly in an object literal.
const amountAttribute = 'amount';
const foo = {
[amountAttribute]: 1
};
foo[amountAttribute + "__more"] = 2;
Results in:
{
amount: 1,
amount__more: 2
}
You can also add new json objects into your json, using the extend function,
var newJson = $.extend({}, {my:"json"}, {other:"json"});
// result -> {my: "json", other: "json"}
A very good option for the extend function is the recursive merge. Just add the true value as the first parameter (read the documentation for more options). Example,
var newJson = $.extend(true, {}, {
my:"json",
nestedJson: {a1:1, a2:2}
}, {
other:"json",
nestedJson: {b1:1, b2:2}
});
// result -> {my: "json", other: "json", nestedJson: {a1:1, a2:2, b1:1, b2:2}}
Uses $.extend() of jquery, like this:
token = {_token:window.Laravel.csrfToken};
data = {v1:'asdass',v2:'sdfsdf'}
dat = $.extend(token,data);
I hope you serve them.
Following worked for me for add a new field named 'id'.
Angular Slickgrid usually needs such id
addId() {
this.apiData.forEach((item, index) => {
item.id = index+1;
});

Categories

Resources