>>> from re import compile
    >>> atomics = (True, 1, 1.0, '', None, compile(''), datetime.now(), b'')
    >>> any(needs_encode(i) for i in atomics)
    False
    >>> needs_encode([1, 2, 3])
    False
    >>> needs_encode([])
    False
    >>> needs_encode([1, [2, 3]])
    False
    >>> needs_encode({})
    False
    >>> needs_encode({'1': {'2': 3}})
    False
    >>> needs_encode({'1': [2]})
    False
    >>> needs_encode(b'1')
    False

    Objects that don't round trip need encoding::

    >>> needs_encode(tuple())
    True
    >>> needs_encode(set())
    True
    >>> needs_encode([1, [set()]])
    True
    >>> needs_encode({'1': {'2': set()}})
    True

    Mongo rejects dicts with non-string keys so they need encoding too::

    >>> needs_encode({1: 2})
    True
    >>> needs_encode({'1': {None: True}})
    True

def needs_encode(obj):
    '''
    >>> from re import compile
    >>> atomics = (True, 1, 1.0, '', None, compile(''), datetime.now(), b'')
    >>> any(needs_encode(i) for i in atomics)
    False
    >>> needs_encode([1, 2, 3])
    False
    >>> needs_encode([])
    False
    >>> needs_encode([1, [2, 3]])
    False
    >>> needs_encode({})
    False
    >>> needs_encode({'1': {'2': 3}})
    False
    >>> needs_encode({'1': [2]})
    False
    >>> needs_encode(b'1')
    False

    Objects that don't round trip need encoding::

    >>> needs_encode(tuple())
    True
    >>> needs_encode(set())
    True
    >>> needs_encode([1, [set()]])
    True
    >>> needs_encode({'1': {'2': set()}})
    True

    Mongo rejects dicts with non-string keys so they need encoding too::

    >>> needs_encode({1: 2})
    True
    >>> needs_encode({'1': {None: True}})
    True


    '''
    obtype = type(obj)
    if obtype in atomic_types:
        return False
    if obtype is list:
        return any(needs_encode(i) for i in obj)
    if obtype is dict:
        return any(type(k) not in valid_key_types or needs_encode(v)
                   for (k, v) in obj.items())
    return True