Getting and checking value from a mixed object array HTTP endpoint

I try to get location data by a external HTTP call. For this API, any part in the data can be missing. Initially I wrote the following code to check whether data.res.accounts[0].contacts[0].location exists:

const getLocation = async () => {
const data = await fetchLocation();
if (
data == null
data.res == null ||
data.res.accounts == null ||
data.res.accounts[0] == null ||
data.res.accounts[0].contacts == null ||
data.res.accounts[0].contacts[0] == null ||
data.res.accounts[0].contacts[0].location == null
) {
return null;
}
return data.res.accounts[0].contacts[0].location;
}


I have tried to simplify the mixed object and array code. One way of doing this is using JavaScript's new conditional operator ?..

For example if they are all objects then I can use something like:

if (data?.res?.account?.contact?.location == null) {
return null;
}


However my data is mixed with arrays and objects. This is the best I've come up with:

if (
data?.res?.accounts == null ||
data.res.accounts[0]?.contacts == null ||
data.res.accounts[0].contacts[0]?.location == null
) {
return null;
}


Can I simplify it further?

• This question lacks any indication of what the code is intended to achieve. To help reviewers give you better answers, please add sufficient context to your question, including a title that summarises the purpose of the code. We want to know why much more than how. The more you tell us about what your code is for, the easier it will be for reviewers to help you. The title needs an edit to simply state the task. Sep 14 '20 at 21:24
• @SᴀᴍOnᴇᴌᴀ thanks for the guide, just added more details. Sep 14 '20 at 21:31
• Hey the code you have posted looks like it's missing context to be reasonably reviewed. As it looks like a generic best practice question. Could you provide more code as this looks like an example, and the answer to best practices is "it depends" Sep 14 '20 at 21:33
• @Peilonrayz thanks, added more details again. Sep 14 '20 at 21:42
• Hey @HongboMiao I think you've added more text in response to my comment. I have edited your text, but I don't think the problem lies there. My problem with your post is that there is a lack of surrounding code, that can give us insight on if this is a good idea. Currently for me to write an answer I'd need to write a short novel on how ?. can be used in any situation, because I can't see what your situation is. Sep 14 '20 at 22:14

Optional chaining can be used not only with object property lookup with dot notation, but also with bracket notation, by putting ?. before the [:

const data = { res: {}};
console.log(data?.res?.accounts?.[0]?.contacts?.[0]?.location == null)

Just to be thorough, you can also use optional chaining with function calls:

const data = { res: { }};
console.log(data?.res?.accounts?.[0]?.contacts?.[0].location?.includes?.('foobar'));

const data2 = { res: { accounts: [{ contacts: [{ location: 'foobarbaz' }] }] } };
console.log(data2?.res?.accounts?.[0]?.contacts?.[0].location?.includes?.('foobar'));

const data3 = { res: { accounts: [{ contacts: [{ location: 7 }] }] } };
console.log(data3?.res?.accounts?.[0]?.contacts?.[0].location?.includes?.('foobar'));

That said, having so many nested properties whose existence is uncertain is a bit of a code smell, because any time you want to look something up, you'll have to check the existence of all intermediate properties first. Despite the fact that you can do it concisely with optional chaining nowadays, it's quite a strange data structure. If you have control over fetchLocation, consider if you can refactor it so that fewer intermediate properties may be missing. (For example, if you could change it so that every account has a contacts array, and the array might be empty (but not null nor undefined), that'd be a step in the right direction).

Also, keep in mind that optional chaining is extremely new syntax. If this is going to be running on a public website, make sure to transpile your code down to ES6 or ES5 for production so that those with older browsers can consume your code.

On another note, you might consider using === instead of ==. ESLint rule: eqeqeq. == has some pretty odd behaviors involving type coercion that developers shouldn't be forced to look up in order to be 100% confident of what logic a particular section of code is implementing. For example, you could use ?? instead, to handle cases of undefined or null:

const location = data?.res?.accounts?.[0]?.contacts?.[0]?.location;
return location ?? null;


(Or, if location will always be a non-empty string, you could return location || null, which might be a bit better)

Also, the conditional operator is not in any of the code in your question or this answer. The conditional operator uses syntax of the form:

const resultingExpression = condition ? expression1 : expression2;

• Really appreciate, both accounts?.[0]? and nullish coalescing operator ?? are new to me! Sep 15 '20 at 15:07

If you use the Nullish Coalescing Operator you can do something similar to the following:

const getLocation = async () => {
const nestMap = ['res', 'accounts', 0, 'contacts', 0, 'location'];
const data = await fetchLocation();

const searchNest = (data, property) => (data ?? false) ?
data[property] : null;

return nestMap.reduce(searchNest, data)
}


This basically takes a predetermined list of properties (nestMap) and proceeds to check an input object (data) for any nullish value down the list of properties. If it reaches the end of the nest without encountering null it returns the last property in the nestmap (location in this case) otherwise it returns null.

Note: If the returned location is undefined you will get null just like your own code returns.

Not sure if this is what you are looking for but it might provide some insights into simplifying either way.