class Dictable(object):
    _cached_props = None

    @classmethod
    def _get_cached_props(cls):
        if cls._cached_props is None:
            props = set()
            for c in cls.mro():
                props.update(k for k, v in vars(c).items() if isinstance(v, property) and not k.startswith('_'))
            cls._cached_props = list(props)
        return cls._cached_props

    def to_dict(self, **extra):
        props = self._get_cached_props()
        d = {k: getattr(self, k) for k in props if getattr(self, k)}
        res = {k: (v.to_dict() if isinstance(v, Dictable) else v) for k, v in d.items()}
        if extra:
            # add the extra items to our result, make sure not to overwrite existing properties (claims etc)
            res.update({k: v for k, v in extra.items() if k not in props})
        return res

    @classmethod
    def from_dict(cls, d):
        return cls(**d)