1

I am trying to destructure an object conditionally but even trying to implement fallbacks I saw on other answers on here doesn't help. Sometimes either departure or arrival are undefined which makes the code break. How could I solve it?

const {
    duration,
    segments: [
        {
            departure: { iataCode: departureIataCode, at: departAt},
            arrival: { iataCode: arrivalIataCode, at: arriveAt },
            carrierCode,
        },
        {
            departure: {
                iataCode: stopOverDepartureIataCode,
                at: stopOverDepartAt,
            },
            arrival: { iataCode: finalArrivalIataCode, at: finalArriveAt },
        },
    ],
} = outbound;
illyria
  • 324
  • 4
  • 19
  • Destructure level by level – Roberto Zvjerković Dec 09 '21 at 08:21
  • 3
    I would say instead of trying to use conditions during destructure assignment, use it to prep your object for being destructured. Of course it may be more readable to avoid destructure assignment in the first place here. – async await Dec 09 '21 at 08:34
  • My suggestion is, keep a default object and then use it to fill object. like `const {...} = _.cloneDeep(defaultObj, outbound)` – Rajesh Dec 09 '21 at 08:45

2 Answers2

1

This might not be a direct answer but is definitely related and helpful to some...

I created this function a while back inspired by Laravels data_get() php function:

/**
 * Get an item from an array or object using "dot" notation.
 *
 * @param {object} source
 * @param {array|string} name
 * @param {null|*} defaultValue
 *
 * @returns {null|*}
 */
window.objectGet = function( source, name, defaultValue = null ) {

    const parts = Array.isArray( name ) ? name : name.split( '.' );

    // Duplicate array prevents modifying original by reference
    let partsTracking = Array.isArray( name ) ? name : name.split( '.' );

    for ( let i = 0; i < parts.length; i++ ) {
        const segment = parts[ i ];
        partsTracking.splice( 0, 1 );

        if ( segment === null ) {
            return source;
        }

        if ( segment === '*' ) {

            if ( !source || typeof source !== 'object' ) {
                return defaultValue;
            }

            let tmpResult = [];

            for ( var key in source ) {
                tmpResult.push( objectGet( source[ key ], partsTracking.join( '.' ), defaultValue ) );
            }

            return partsTracking.includes( '*' ) ? tmpResult.flat( Infinity ) : tmpResult;
        }

        if ( source && typeof source === 'object' && segment in source ) {
            source = source[ segment ];
        } else {
            return defaultValue;
        }
    }

    return source;
};

Example usage:

const myObject = {
    key: 'value',
    nested: {
        example: [
            'value1',
            'value2',
        ]
    }
};

console.log( objectGet( myObject, 'key' ) ); // Output: 'value'
console.log( objectGet( myObject, 'nested.example' ) ); // Output: [ 'value1', 'value2' ]
console.log( objectGet( myObject, 'nested.example.0' ) ); // Output: 'value1'
console.log( objectGet( myObject, 'nested.example.1' ) ); // Output: 'value2'

So usage in regards to OPs question might be something like:

const duration                  = objectGet( outbound, 'duration' );
const departureIataCode         = objectGet( outbound, 'segments.0.departure.iataCode' );
const departAt                  = objectGet( outbound, 'segments.0.departure.at' );
const arrivalIataCode           = objectGet( outbound, 'segments.0.arrival.iataCode' );
const arriveAt                  = objectGet( outbound, 'segments.0.arrival.at' );
const carrierCode               = objectGet( outbound, 'segments.0.carrierCode' );
const stopOverDepartureIataCode = objectGet( outbound, 'segments.1.departure.iataCode' );
const stopOverDepartAt          = objectGet( outbound, 'segments.1.departure.at' );
const finalArrivalIataCode      = objectGet( outbound, 'segments.1.arrival.iataCode' );
const finalArriveAt             = objectGet( outbound, 'segments.1.arrival.at' );

I should add that if a key isn't found, null will be returned. You can pass default values as the third parameter, for example:

const notFound = objectGet( input, 'my.data.key', 'my default value' );

You can also return an array of nest values by using * for example:

const input = {
    data: [
        {
            id: 123,
            name: 'Example 1'
        },
        {
            id: 789,
            name: 'Example 2'
        }
    ]
};

const ids = objectGet( input, 'data.*.id' ); // Ouput: [ 123, 789 ]
Levi Cole
  • 3,561
  • 1
  • 21
  • 36
  • [Accessing nested JavaScript objects and arrays by string path](https://stackoverflow.com/q/6491463) – VLAZ Dec 09 '21 at 09:10
  • @VLAZ Yes! Pretty much the same as what I've put above. My function also includes an `*` operator which will return an array of nested values (I've added an example to my answer). – Levi Cole Dec 09 '21 at 09:58
1

If you provide defaults when destructuring, you won't get an error, rather the values will be set to some default value if not present.

In the example below I've removed the arrival object from the first segment and the stop over iata code, these will be set to the default value rather than causing an error.

let outbound = {
    duration: 'duration',
    segments: [
        {
            departure: { iataCode: 'LAX', at: 'departAt'},
            carrierCode: 'LH',
        },
        {
            departure: {
                at: 'stopOverDepartAt',
            },
            arrival: { iataCode: 'finalArrivalIataCode', at: 'finalArriveAt' },
        },
    ],
};

// Set to whichever value you wish...
const defaultValue = undefined;

const {
    duration,
    segments: [
        {
            departure: { iataCode: departureIataCode = defaultValue, at: departAt = defaultValue } = {},
            arrival: { iataCode: arrivalIataCode = defaultValue, at: arriveAt = defaultValue } = {},
            carrierCode = '',
        } = {}, // Set default for segment 0.
        {
            departure: {
                iataCode: stopOverDepartureIataCode = defaultValue,
                at: stopOverDepartAt = defaultValue,
            } = {},
            arrival: { iataCode: finalArrivalIataCode = defaultValue, at: finalArriveAt = defaultValue } = {},
        } = {}, // Set default for segment 1.
    ] = [{},{}], // Set defaults for segments.
} = outbound;

let outputs = { departureIataCode, carrierCode, arrivalIataCode, stopOverDepartureIataCode } ;
Object.keys(outputs).forEach(k => console.log(`${k}:`, `${outputs[k]}`))
         
.as-console-wrapper { max-height: 100% !important; top: 0; }

I'd also take a look at lodash get, this is very good for accessing nested values that may or may not be present:

let outbound = {
    duration: 'duration',
    segments: [
        {
            departure: { iataCode: 'LAX', at: 'departAt'},
            carrierCode: 'LH',
        },
        {
            departure: {
                at: 'stopOverDepartAt',
            },
            arrival: { iataCode: 'finalArrivalIataCode', at: 'finalArriveAt' },
        },
    ],
};

let departureIataCode = _.get(outbound, 'segments[0].departure.iataCode');
let carrierCode = _.get(outbound, 'segments[0].carrierCode');
let arrivalIataCode = _.get(outbound, 'segments[0].arrival.iataCode');
let stopOverDepartureIataCode = _.get(outbound, 'segments[1].departure.iataCode');

let outputs = { departureIataCode, carrierCode, arrivalIataCode, stopOverDepartureIataCode } ;
Object.keys(outputs).forEach(k => console.log(`${k}:`, `${outputs[k]}`))
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" referrerpolicy="no-referrer"></script>
Terry Lennox
  • 29,471
  • 5
  • 28
  • 40
  • 1
    Hey, thank you for your answer. If I didn't get it wrong that would work but I am destructuring fetched data, and my problem is that the entire objects departure or arrival might not be present, and I can't give them default values (I think at least( – illyria Dec 09 '21 at 10:16
  • I've given them default values of {}, e.g. empty objects, see the line: departure: { iataCode: departureIataCode = defaultValue, at: departAt = defaultValue } _= {}_, this should work if they are not present. The lodash solution would work in the same way! – Terry Lennox Dec 09 '21 at 10:19