def get_obj_attr(object, item, missing_value=None, join_with=None, transform=None):
    """
    Returns the value of an object's attribute, checking if it exists. It can provide a predefined default value,
    in case it's an array can be joined with supplied char, and can be even transformed with supplied lambda function
    :param object: The object to look for the attribute
    :param item: The name of the field to retrieve
    :param missing_value: Default value in case it doesn't exists
    :param join_with: If the field value is an array, join the items with supplied char
    :param transform: Apply supplied transformation to the field, or to each member of the field if it's
    an array. The supplied function, must consider the type of data that the field value should contain, that is,
    we can not apply an upper() to an integer, for example.
    :return: The value of the field, or the default value if it doesn't exists and, optionally, transformed with
    supplied function or if it's an array, a single value with it's items joined
    """
    # traverse fields if several are provided joined by a dot
    fields = item.split('.')
    item = fields[-1]

    for _child in fields[:-1]:
        if hasattr(object, _child):
            object = getattr(object, _child)
        else:
            return missing_value

    if not hasattr(object, item):
        return missing_value

    value = getattr(object, item)

    if not isinstance(value, list):
        if transform:
            return transform(value)
        else:
            return value
    else:
        if transform and join_with:
            assert isinstance(join_with, str), f'{join_with} must be a string'
            return join_with.join([transform(x) for x in value])
        elif transform:
            return list(map(transform, value))
        elif join_with:
            return join_with.join(value)
        else:
            return value