Ensures the specified object is serializable, converting if necessary.

    :param obj: the object to use.
    :param on_filter: optional function that can be used to filter which
                      properties on the object will be included.
    :return value representing the object, which is serializable.

def serializable(obj: Any, on_filter: Callable[[Any, str], bool] = None) -> Any:
    """
    Ensures the specified object is serializable, converting if necessary.

    :param obj: the object to use.
    :param on_filter: optional function that can be used to filter which
                      properties on the object will be included.
    :return value representing the object, which is serializable.
    """

    # Will be called recursively when object has children
    def _serializable(parent_obj: Any, obj: Any,
                      on_filter: Callable[[Any, str], bool]) -> Any:
        # None can be left as-is
        if obj is None:
            return obj

        # IntFlag enums should be broken down to a list of names
        elif isinstance(obj, IntFlag):
            value = str(obj)
            if value == '0':
                return None
            return value.split('|')

        # Any other enum just use the name
        elif isinstance(obj, Enum):
            return str(obj)

        # Simple types can be left as-is
        elif isinstance(obj, (bool, int, float, str)):
            return obj

        # Class supports method to convert to serializable dictionary; use it
        elif hasattr(obj, 'as_dict') and parent_obj is not None:
            return obj.as_dict()

        elif isinstance(obj, dict):
            # Dictionaries will require us to check each key and value
            new_dict = {}
            for item in obj.items():
                new_dict[_serializable(obj, item[0], on_filter=on_filter)] = \
                    _serializable(obj, item[1], on_filter=on_filter)
            return new_dict

        elif isinstance(obj, (list, Container)):
            # Lists will require us to check each item
            items = obj # type: Iterable
            new_list = []
            for item in items:
                new_list.append(_serializable(obj, item, on_filter=on_filter))
            return new_list

        # Convert to a dictionary of property name/values
        data = {}
        for name in dir(obj.__class__):
            if not isinstance(getattr(obj.__class__, name), property):
                continue
            elif on_filter and not on_filter(obj, name):
                continue
            value = getattr(obj, name)
            data[name] = _serializable(obj, value, on_filter=on_filter)
        return data

    return _serializable(None, obj, on_filter)