Add property to object when it's not null - javascript

I'm working on a small API and I want to update the data using HTTP PATCH REQUEST without using a bunch of if statements. I'm trying to fill the outgoing data object with the changed data only.
update() {
let prop1 = hasBeenChanged.prop1 ? changedData.prop1 : null;
// ...
let propN = hasBeenChanged.propN ? changedData.propN : null;
let data: ISomething = {
// something like --> property != null ? property: property.value : nothing
}
}
Is there any way to create the data object dynamically?

You could use Object.assign in combination with the ternary operator:
let data = Object.assign({},
first === null ? null : {first},
...
);
This works because Object.assign will skip over null parameters.
If you are sure that the property value is not going to be "falsy", then it would be bit shorter to write:
let data = Object.assign({},
first && {first},
...
);
Assuming the object is going to be stringified at some point, since stringification ignores undefined values, you could also try
let data = {
first: first === null ? undefined : first,
...
}

Depending on the JS version you are using, you can use the spread operator ...
const getData = data => ({
...data.first && { 'Custom First Prop Name': data.first },
...data.second && { 'Custom Second Prop Name': data.second },
...data.third && { third: data.third },
...data.fourth && { fourth: data.fourth },
});

Non-inline solution
If you don't need to add the value inline, it is pretty straightforward and clean to write your assignment like this.
const val1 = 1
const val2
const data = {}
val1 && (data.a = val1) // 1
val2 && (data.b = val2) // Not added
// data = { a : 1 }
NOTE: This will not work if the value is falsey

Related

why cant I access the object values within state when they are clearly shown? [duplicate]

In my code, I deal with an array that has some entries with many objects nested inside one another, where as some do not. It looks something like the following:
// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
This is giving me problems because I need to iterate through the array at times, and the inconsistency is throwing me errors like so:
for (i=0; i<test.length; i++) {
// ok on i==0, but 'cannot read property of undefined' on i==1
console.log(a.b.c);
}
I am aware that I can say if(a.b){ console.log(a.b.c)}, but this is extraordinarily tedious in cases where there are up to 5 or 6 objects nested within one another. Is there any other (easier) way that I can have it ONLY do the console.log if it exists, but without throwing an error?
Update:
If you use JavaScript according to ECMAScript 2020 or later, see optional chaining.
TypeScript has added support for optional chaining in version 3.7.
// use it like this
obj?.a?.lot?.of?.properties
Solution for JavaScript before ECMASCript 2020 or TypeScript older than version 3.7:
A quick workaround is using a try/catch helper function with ES6 arrow function:
function getSafe(fn, defaultVal) {
try {
return fn();
} catch (e) {
return defaultVal;
}
}
// use it like this
console.log(getSafe(() => obj.a.lot.of.properties));
// or add an optional default value
console.log(getSafe(() => obj.a.lot.of.properties, 'nothing'));
What you are doing raises an exception (and rightfully so).
You can always do:
try{
window.a.b.c
}catch(e){
console.log("YO",e)
}
But I wouldn't, instead think of your use case.
Why are you accessing data, 6 levels nested that you are unfamiliar of? What use case justifies this?
Usually, you'd like to actually validate what sort of object you're dealing with.
Also, on a side note you should not use statements like if(a.b) because it will return false if a.b is 0 or even if it is "0". Instead check if a.b !== undefined
If I am understanding your question correctly, you want the safest way to determine if an object contains a property.
The easiest way is to use the in operator.
window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
//true
}
if ("b" in window){
//false
}
Of course you can nest this as deep as you want
if ("a" in window.b.c) { }
Not sure if this helps.
Try this. If a.b is undefined, it will leave the if statement without any exception.
if (a.b && a.b.c) {
console.log(a.b.c);
}
If you are using lodash, you could use their has function. It is similar to the native "in", but allows paths.
var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
//Safely access your walrus here
}
If you use Babel, you can already use the optional chaining syntax with #babel/plugin-proposal-optional-chaining Babel plugin. This would allow you to replace this:
console.log(a && a.b && a.b.c);
with this:
console.log(a?.b?.c);
If you have lodash you can use its .get method
_.get(a, 'b.c.d.e')
or give it a default value
_.get(a, 'b.c.d.e', default)
I use undefsafe religiously. It tests each level down into your object until it either gets the value you asked for, or it returns "undefined". But never errors.
This is a common issue when working with deep or complex json object, so I try to avoid try/catch or embedding multiple checks which would make the code unreadable, I usually use this little piece of code in all my procect to do the job.
/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
* accepts array for property names:
* getProperty(myObj,['aze','xyz'],{value: null})
*/
function getProperty(obj, props, defaultValue) {
var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
if(!isvoid(obj)){
if(isvoid(props)) props = [];
if(typeof props === "string") props = props.trim().split(".");
if(props.constructor === Array){
res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
}
}
return typeof res === "undefined" ? defaultValue: res;
}
I like Cao Shouguang's answer, but I am not fond of passing a function as parameter into the getSafe function each time I do the call. I have modified the getSafe function to accept simple parameters and pure ES5.
/**
* Safely get object properties.
* #param {*} prop The property of the object to retrieve
* #param {*} defaultVal The value returned if the property value does not exist
* #returns If property of object exists it is returned,
* else the default value is returned.
* #example
* var myObj = {a : {b : 'c'} };
* var value;
*
* value = getSafe(myObj.a.b,'No Value'); //returns c
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
*
* if (getSafe(myObj.a.x, false)){
* console.log('Found')
* } else {
* console.log('Not Found')
* }; //logs 'Not Found'
*
* if(value = getSafe(myObj.a.b, false)){
* console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
return function(fn, defaultVal) {
try {
if (fn() === undefined) {
return defaultVal;
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}(function() {return prop}, defaultVal);
}
Lodash has a get method which allows for a default as an optional third parameter, as show below:
const myObject = {
has: 'some',
missing: {
vars: true
}
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Imagine that we want to apply a series of functions to x if and only if x is non-null:
if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);
Now let's say that we need to do the same to y:
if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);
And the same to z:
if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);
As you can see without a proper abstraction, we'll end up duplicating code over and over again. Such an abstraction already exists: the Maybe monad.
The Maybe monad holds both a value and a computational context:
The monad keeps the value safe and applies functions to it.
The computational context is a null check before applying a function.
A naive implementation would look like this:
⚠️ This implementation is for illustration purpose only! This is not how it should be done and is wrong at many levels. However this should give you a better idea of what I am talking about.
As you can see nothing can break:
We apply a series of functions to our value
If at any point, the value becomes null (or undefined) we just don't apply any function anymore.
const abc = obj =>
Maybe
.of(obj)
.map(o => o.a)
.map(o => o.b)
.map(o => o.c)
.value;
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script>
function Maybe(x) {
this.value = x; //-> container for our value
}
Maybe.of = x => new Maybe(x);
Maybe.prototype.map = function (fn) {
if (this.value == null) { //-> computational context
return this;
}
return Maybe.of(fn(this.value));
};
</script>
Appendix 1
I cannot explain what monads are as this is not the purpose of this post and there are people out there better at this than I am. However as Eric Elliot said in hist blog post JavaScript Monads Made Simple:
Regardless of your skill level or understanding of category theory, using monads makes your code easier to work with. Failing to take advantage of monads may make your code harder to work with (e.g., callback hell, nested conditional branches, more verbosity).
Appendix 2
Here's how I'd solve your issue using the Maybe monad from monetjs
const prop = key => obj => Maybe.fromNull(obj[key]);
const abc = obj =>
Maybe
.fromNull(obj)
.flatMap(prop('a'))
.flatMap(prop('b'))
.flatMap(prop('c'))
.orSome('🌯')
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script src="https://www.unpkg.com/monet#0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>
In str's answer, value 'undefined' will be returned instead of the set default value if the property is undefined. This sometimes can cause bugs. The following will make sure the defaultVal will always be returned when either the property or the object is undefined.
const temp = {};
console.log(getSafe(()=>temp.prop, '0'));
function getSafe(fn, defaultVal) {
try {
if (fn() === undefined || fn() === null) {
return defaultVal
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}
You can use optional chaining from the ECMAScript standart.
Like this:
a?.b?.c?.d?.func?.()
I answered this before and happened to be doing a similar check today. A simplification to check if a nested dotted property exists. You could modify this to return the value, or some default to accomplish your goal.
function containsProperty(instance, propertyName) {
// make an array of properties to walk through because propertyName can be nested
// ex "test.test2.test.test"
let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];
// walk the tree - if any property does not exist then return false
for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {
// property does not exist
if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
return false;
}
// does it exist - reassign the leaf
instance = instance[walkArr[treeDepth]];
}
// default
return true;
}
In your question you could do something like:
let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');
I usually use like this:
var x = object.any ? object.any.a : 'def';
You can avoid getting an error by giving a default value before getting the property
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
for (i=0; i<test.length; i++) {
const obj = test[i]
// No error, just undefined, which is ok
console.log(((obj.a || {}).b || {}).c);
}
This works great with arrays too:
const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)
Unrelated to the question's actual question, but might be useful for people coming to this question looking for answers.
Check your function parameters.
If you have a function like const x({ a }) => { }, and you call it without arguments x(); append = {} to the parameter: const x({ a } = {}) => { }.
What I had
I had a function like this:
const x = ({ a }) => console.log(a);
// This one works as expected
x({ a: 1 });
// This one errors out
x();
Which results in "Uncaught TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined."
What I switched it to (now works).
const x = ({ a } = {}) => console.log(a);
// This one works as expected
x({ a: 1 });
// This now works too!
x();

Conditionally add properties to an object if referencing variable contains string

Some state
const [submittedSev, setSubmittedSev] = useState('');
const [newSev, setNewSev] = useState('');
Mixpanel events where I'm sending properties that may or may not exist. Sometimes the submittedSev and newSev may be empty strings but this will still send
mixpanel.track('COMPLETED', {
response: props.details.title,
submittedSev,
newSev
});
I'd like to only add submittedSev and newSev properties if the string isn't empty. For sure I could set up a conditional statement and check the string length and send a different Mixpanel event but that doesn't seem succinct enough.
Output of data that sometimes gets sent
{
"response": "hello",
"submittedSev": "",
"newSev": ""
}
How can I only add properties to object if they are not empty strings?
Here's a really concise way to do it using the spread ... operator and evaluating an expression with the AND operator && which returns the second value if the first is truthy:
// Example.js
const name = ''
const email = 'asd#gmail.com'
const phone = ''
const output = {
...(name && {name}),
...(email && {email}),
...(phone && {phone})
}
console.log(output)
So for your code it'd be:
mixpanel.track('COMPLETED', {
response: props.details.title,
...(submittedSev && {submittedSev}),
...(newSev && {newSev})
});
You could centralize the logic that sends the events in a separate function, and go through the properties of the event object and filter out the ones that are not empty strings:
function trackMixpanelEvent(eventType, event) {
const keys = Object.keys(event).filter(
key => Object.prototype.hasOwnProperty.call(event, key));
keys.forEach(key => {
if (event[key] === '') {
delete event[key];
}
});
mixpanel.track(eventType, event);
}
And then just call it without worrying which properties are empty strings:
trackMixpanelEvent('COMPLETED', {
response: props.details.title,
submittedSev,
newSev
});

Conditially using an object property

I have the following ternary:
exceptionType == 'Asset' ? selection = selectedAssets.find(function(obj) {return obj.fixedTitle === element}) : selection = dedupedAssets.find(function(obj) {return obj.fixedTitle === element});
I am conditionally assigning the variable selection to the value returned by find(). The functions are extremely similar, with the only difference being the array that is targeted.
Is there a way I can shorten this even further?
You could use the ternary operator to know what items to be iterated on and use the logic run once instead of duplicating it.
const itemsToIterate = exceptionType == 'Asset' ? selectedAssets : dedupedAssets;
const items = itemsToIterate.find(function(obj) {return obj.fixedTitle === element});
You can tweak this a bit to make it even shorter.
const itemsToIterate = exceptionType == 'Asset' ? selectedAssets : dedupedAssets;
const items = itemsToIterate.find(({ fixedTitle }) => fixedTitle === element);
You can use your boolean condition as an index to get the right array to apply find to:
const selection = [dedupedAssets, selectedAssets][+(exceptionType === 'Asset')]
.find(o => o.fixedTitle === element);
Using the operator +, false will be converted to 0 and dedupedAssets will be returned, and true will be converted to 1 and selectedAssets will be returned.
Indexing example
const a = [{ title: 'hello' }];
const b = [{ title: 'world' }];
console.log([a, b][+false].find(o => o.title === 'hello'));
console.log([a, b][+true].find(o => o.title === 'world'));

Set property on object while iterating

I am making a SPA with Laravel(backend) and Vue.js. I have the following arrays:
accessArray:
["BIU","CEO","Finance","HRD","Group"]
access:
["BIU","Group"]
I want to compare the access array to the accessArray array and if there is a match to change the record (in the accessArray) and add a true value otherwise add a false value. I am doing this inside a Vue method.
... so far I got this:
var foo = ["BIU","CEO","Finance","HRD","Group"];
var bar = ["BIU","Group"];
$.each(bar, function (key, value) {
if ($.inArray(value, foo) != -1) {
var position = $.inArray(value, foo);
console.log(value + ' is in the array. In position ' + position);
foo[position] = {name: value, checked: true};
}
});
Which outputs this to the console:
BIU is in the array. In position 0
Group is in the array. In position 4
And this in Vue:
[
{"name":"BIU","checked":true},
"CEO",
"Finance",
"HRD",
{"name":"Group","checked":true}
]
The output I would like to achieve is the following:
[
{"name":"BIU","checked":true},
{"name":"CEO","checked":false},
{"name":"Finance","checked":false},
{"name":"HRD","checked":false},
{"name":"Group","checked":true}
]
Any help would be greatly appreciated, I have looked at many similar problems on SO but cant seem to find anything along these lines. I have also tried to add an else statement on the end but I (think) I'm converting it to an object so that doesn't seem to work.
Edit:
The data in foo comes from a Laravel config setting so is somewhat dynamic
The data in bar is JSON received from the Laravel ORM (its json stored in a text field)
An option with vanilla javascript:
var foo = ["BIU","CEO","Finance","HRD","Group"];
var bar = ["BIU","Group"];
var result = foo.map(name => {
var checked = bar.indexOf(name) !== -1
return { name, checked }
})
console.log(result)
You can use Array#map to iterate over the array and construct a new one, by checking if values are present in the other one through Array#includes
const accessArray = ["BIU","CEO","Finance","HRD","Group"];
const access = [ "BIU", "Group" ];
const result = accessArray.map( a => ({ name: a, checked: access.includes(a)}) ) ;
console.log(result);
A note: when using an arrow function and you want to return an object, you need to surround the object literal in () otherwise it would be interpreted as a code block.
Use reduce and inside the reduce call back check if the item is present in both accessArray & access . Create an object and the item present in both array set the value of checked to true or false
let arr1 = ["BIU", "CEO", "Finance", "HRD", "Group"]
let arr2 = ["BIU", "Group"];
let k = arr1.reduce(function(acc, curr) {
let obj = {}
if (arr2.includes(curr)) {
obj.name = curr;
obj.checked = true
} else {
obj.name = curr;
obj.checked = false
}
acc.push(obj);
return acc;
}, []);
console.log(k)
To achieve expected result use below option
1. Loop foo array
2.Remove initial if condition - "if ($.inArray(value, foo) != -1)" to loop through all
3. Do conditional check for checked - checked: $.inArray(value, bar) !== -1 ? true : false
codepen - https://codepen.io/nagasai/pen/GXbQOw?editors=1011
var foo = ["BIU","CEO","Finance","HRD","Group"];
var bar = ["BIU","Group"];
$.each(foo, function (key, value) {
foo[key] = {name: value, checked: $.inArray(value, bar) !== -1 ? true : false};
});
console.log(foo);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Option 2:
Without using jquery and using simple forEach to loop through foo
codepen - https://codepen.io/nagasai/pen/YOoaNb
var foo = ["BIU","CEO","Finance","HRD","Group"];
var bar = ["BIU","Group"];
foo.forEach((v,i) => foo[i] = {name: v , checked : bar.includes(v)})
console.log(foo);

toString all the object datas

I have an object (json) like this in node.js:
var data = {
string : "name",
number : 123456789 ,
n : null ,
bool : false ,
bool2 : true
};
But I need to conver it to something like this:
{
string : "name",
number : "123456789" ,
n : "null" ,
bool : "false" ,
bool2 : "true"
};
I used this codes but not works.
for ( var index in data ){
data[index] = data[index].toString();
};
// or this
data.toString();
How can I fix it?
UPDATE
this data object is created as a new mongoose schema.
Your code looks fine, except for one thing: null doesn't have .toString() method. So, it's best to use String instead:
for ( var key in data ){
data[key] = String(data[key]);
};
String is a string constructor. It takes anything and produces a string representation of it.
Update
But this solution won't work for complex data structures. Though, if you need a JSON string, then you could use JSON.stringify with tricky replacer:
function replaceWithString(k, v) {
if ((typeof v === 'object') && (v !== null)) {
return v;
} else {
return String(v);
}
}
JSON.stringify(data, replaceWithString);
and if you want to make it pretty:
JSON.stringify(data, replaceWithString, 2);
N.B. As Bergi noticed in comments, you could use Object(v) === v instead of (typeof v === 'object') && (v !== null) to check that v is an object.
Update2
It looks like data in your example is a mongoose document.
The problem with mongoose is that it wraps all its object with the whole pack of nasty getters and setters to make them look like plain JS objects, which they are not.
So, if you're working with mongoose documents, you should call .toObject() or .toJSON() before trying to do anything with it:
data = doc.toObject(); // converts doc to plain JS object
Though, my second solution with JSON.stringify should work anyway, because stringify calls .toJSON() automatically.
for (var index in data) {
if (data[index] === null) {
data[index] = "null";
}
else if (data[index] === undefined) {
data[index] = "undefined";
}
else {
data[index] = data[index].toString();
}
}
Try this:
var val = null;
for(var key in data){
if(data.hasOwnProperty(key)){
val = data[key];
val = val === null ? 'null' : (val === undefined ? 'undefined' : val.toString());
data[key] = val;
}
}
It simply converts null to "null" and undefined to "undefined"
Note that values of your object must be a primitive data type for this to work. btw, this will work fine for your example.
A simple
JSON.stringify(data);
should work.
when doing
data[index].toString();
you are referencing a null on the third run. null has no such method toString().
Just thought I'd answer with a code that's a bit different:
for(var x in data){
data[x] = ""+data[x]+"";
}
Works.

Categories

Resources