docs: 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)