Python block handling empty portions of response

Hello,

I'm new to Workflows (and pretty new to Python in general, so forgive me if this is a simple question). I am ingesting an API with some MDM device details, and then using that to upsert in to a PostgreSQL database.

For API responses where all of the fields have data, this works great. In some cases not all of the fields will have data, and as it stands the workflow just fails when that is the case. How would I add some logic to this block to just pass NULL through if there is no data in a specific field?

Here is the code block I have currently -

data = deviceDetails.data

return [{
    "device_id": deviceDetails.data.general.device_id,
    "device_name": deviceDetails.data.general.device_name,
    "last_enrollment": deviceDetails.data.general.last_enrollment,
    "model": deviceDetails.data.general.model,
    "platform": deviceDetails.data.general.platform,
    "os_version": deviceDetails.data.general.os_version,
    "supplemental_build_version": deviceDetails.data.general.supplemental_build_version,
    "supplemental_os_version_extra": deviceDetails.data.general.supplemental_os_version_extra,
    "system_version": deviceDetails.data.general.system_version,
    "boot_volume": deviceDetails.data.general.boot_volume,
    "time_since_boot": deviceDetails.data.general.time_since_boot,
    "last_user": deviceDetails.data.general.last_user,
    "asset_tag": deviceDetails.data.general.asset_tag,
    "assigned_user_email": deviceDetails.data.general.assigned_user.email,
    "assigned_user_name": deviceDetails.data.general.assigned_user.name,
    "assigned_user_id": deviceDetails.data.general.assigned_user.id,
    "assigned_user_is_archived": deviceDetails.data.general.assigned_user.is_archived,
    "blueprint_name": deviceDetails.data.general.blueprint_name,
    "blueprint_uuid": deviceDetails.data.general.blueprint_uuid,
    "mdm_enabled": deviceDetails.data.mdm.mdm_enabled,
    "mdm_supervised": deviceDetails.data.mdm.supervised,
    "mdm_install_date": deviceDetails.data.mdm.install_date,
    "mdm_last_checkin": deviceDetails.data.mdm.last_check_in,
    "activation_lock_bypass_code_failed": deviceDetails.data.activation_lock.bypass_code_failed,
    "user_activation_lock_enabled": deviceDetails.data.activation_lock.user_activation_lock_enabled,
    "device_activation_lock_enabled": deviceDetails.data.activation_lock.device_activation_lock_enabled,
    "activation_lock_allowed_while_supervised": deviceDetails.data.activation_lock.activation_lock_allowed_while_supervised,
    "activation_lock_supported": deviceDetails.data.activation_lock.activation_lock_supported,
    "filevault_enabled": deviceDetails.data.filevault.filevault_enabled,
    "filevault_recoverykey_type": deviceDetails.data.filevault.filevault_recoverykey_type,
    "filevault_prk_escrowed": deviceDetails.data.filevault.filevault_prk_escrowed,
    "filevault_next_rotation": deviceDetails.data.filevault.filevault_next_rotation,
    "filevault_regen_required": deviceDetails.data.filevault.filevault_regen_required,
    "auto_enrolled_eligible": deviceDetails.data.automated_device_enrollment.auto_enroll_eligible,
    "auto_enrolled": deviceDetails.data.automated_device_enrollment.auto_enrolled,
    "agent_installed": deviceDetails.data.kandji_agent.agent_installed,
    "install_date": deviceDetails.data.kandji_agent.install_date,
    "last_check_in": deviceDetails.data.kandji_agent.last_check_in,
    "agent_version": deviceDetails.data.kandji_agent.agent_version,
    "model_name": deviceDetails.data.hardware_overview.model_name,
    "model_identifier": deviceDetails.data.hardware_overview.model_identifier,
    "processor_name": deviceDetails.data.hardware_overview.processor_name,
    "processor_speed": deviceDetails.data.hardware_overview.processor_speed,
    "number_of_processors": deviceDetails.data.hardware_overview.number_of_processors,
    "total_number_of_cores": deviceDetails.data.hardware_overview.total_number_of_cores,
    "memory": deviceDetails.data.hardware_overview.memory,
    "uuid": deviceDetails.data.hardware_overview.udid,
    "serial_number": deviceDetails.data.hardware_overview.serial_number,
    "local_hostname": deviceDetails.data.network.local_hostname,
    "mac_address": deviceDetails.data.network.mac_address,
    "ip_address": deviceDetails.data.network.ip_address,
    "public_ip": deviceDetails.data.network.public_ip,
    "recovery_lock_enabled": deviceDetails.data.recovery_information.recovery_lock_enabled,
    "firmware_password_exist":deviceDetails.data.recovery_information.firmware_password_exist,
    "firmware_password_pending":deviceDetails.data.recovery_information.firmware_password_pending,
    "abm_model":deviceDetails.data.apple_business_manager.model,
    "abm_color":deviceDetails.data.apple_business_manager.color,
    "abm_description":deviceDetails.data.apple_business_manager.description,
    "abm_serial_number":deviceDetails.data.apple_business_manager.serial_number,
    "abm_device_family":deviceDetails.data.apple_business_manager.device_family,
    "abm_os":deviceDetails.data.apple_business_manager.os,
    "abm_device_assigned_date":deviceDetails.data.apple_business_manager.device_assigned_date,
    "remote_desktop_enabled":deviceDetails.data.security_information.remote_desktop_enabled
}]

Hi @Robby_Barnes ,

Your now assigning the deviceDetails.data to variable data, but then you're not using this variable, in the json, you're referencing to deviceDetails.data.

You can conditionally add data like this using conditional assignments in both JS and Python.

With Python it would be best to assign the values to separate variables first, with something like this.

#Assign var
device_id = deviceDetails.data.general.device_id if deviceDetails.data.general.device_id != "" else null

#Use var in json
"device_id": device_id,

Using a JS block it would be as easy as:

#Directly in json
"device_id": {{deviceDetails.data.general.device_id != "" ? deviceDetails.data.general.device_id null}},
1 Like

This is an interesting question! Did some testing as well and just want to add 2 cents: It looks like Python prefers None to null to denote empty values, and using ternaries to check for empty values looks to be tricky if any of the other objects in the path might be empty as well. This thread has some interesting ideas on implementing something like optional chaining in Python but all of them seem relatively wordy.

I'm wondering what your data looks like if some of the fields are missing @Robby_Barnes. I think, to Marc's point, that a JS block could work really well here, though you might want something like the following:

return [{
"device_id": deviceDetails.data?.general?.device_id,
 /* ...etc */
}]
2 Likes