r"""Make sure the data structure is json serializable (json.dumps-able), all they way down to scalars in nested structures.

    If mutable=False then return tuples for all iterables, except basestrings (strs),
        so that they can be used as keys in a Mapping (dict).

    >>> from collections import OrderedDict
    >>> from decimal import Decimal
    >>> data = {'x': Decimal('01.234567891113151719'), 'X': [{('y', 'z'): {'q': 'A\xFFB'}}, 'ender'] }
    >>> make_serializable(OrderedDict(data)) == {'X': [{('y', 'z'): {'q': 'A\xc3\xbfB'}}, 'ender'], 'x': 1.2345678911131517}
    True
    >>> make_serializable({'ABCs': list('abc'), datetime.datetime(2014,10,31): datetime.datetime(2014,10,31,23,59,59)}
    ...                  ) == {'ABCs': ['2014-10-16 00:00:00', 'b', 'c'], '2014-10-31 00:00:00': '2014-10-31 23:59:59'}
    True

def make_serializable(data, mutable=True, key_stringifier=lambda x:x, simplify_midnight_datetime=True):
    r"""Make sure the data structure is json serializable (json.dumps-able), all they way down to scalars in nested structures.

    If mutable=False then return tuples for all iterables, except basestrings (strs),
        so that they can be used as keys in a Mapping (dict).

    >>> from collections import OrderedDict
    >>> from decimal import Decimal
    >>> data = {'x': Decimal('01.234567891113151719'), 'X': [{('y', 'z'): {'q': 'A\xFFB'}}, 'ender'] }
    >>> make_serializable(OrderedDict(data)) == {'X': [{('y', 'z'): {'q': 'A\xc3\xbfB'}}, 'ender'], 'x': 1.2345678911131517}
    True
    >>> make_serializable({'ABCs': list('abc'), datetime.datetime(2014,10,31): datetime.datetime(2014,10,31,23,59,59)}
    ...                  ) == {'ABCs': ['2014-10-16 00:00:00', 'b', 'c'], '2014-10-31 00:00:00': '2014-10-31 23:59:59'}
    True
    """
    # print 'serializabling: ' + repr(data)
    # print 'type: ' + repr(type(data))


    if isinstance(data, (datetime.datetime, datetime.date, datetime.time)):
        if isinstance(data, datetime.datetime):
            if not any((data.hour, data.miniute, data.seconds)):
                return datetime.date(data.year, data.month, data.day)
            elif data.year == data.month == data.seconds == 1:
                return datetime.time(data.hour, data.minute, data.second)
        return data
        # s = unicode(data)
        # if s.endswith('00:00:00'):
        #     return s[:8]
        # return s
    #print 'nonstring type: ' + repr(type(data))
    elif isinstance(data, Model):
        if isinstance(data, datetime.datetime):
            if not any((data.hour, data.miniute, data.seconds)):
                return datetime.date(data.year, data.month, data.day)
            elif data.year == data.month == data.seconds == 1:
                return datetime.time(data.hour, data.minute, data.second)
        return data
    elif isinstance(data, Mapping):
        mapping = tuple((make_serializable(k, mutable=False, key_stringifier=key_stringifier), make_serializable(v, mutable=mutable)) for (k, v) in data.iteritems())
        # print 'mapping tuple = %s' % repr(mapping)
        #print 'keys list = %s' % repr([make_serializable(k, mutable=False) for k in data])
        # this mutability business is probably unneccessary because the keys of the mapping will already be immutable... at least until python 3 MutableMappings
        if mutable:
            return dict(mapping)
        return mapping
    elif hasattr(data, '__iter__'):
        if mutable:
            #print list(make_serializable(v, mutable=mutable) for v in data)
            return list(make_serializable(v, mutable=mutable) for v in data)
        else:
            #print tuple(make_serializable(v, mutable=mutable) for v in data)
            return key_stringifier(tuple(make_serializable(v, mutable=mutable) for v in data))
    elif isinstance(data, (float, Decimal)):
        return float(data)
    elif isinstance(data, basestring):
        # Data is either a string or some other object class Django.db.models.Model etc
        data = db.clean_utf8(data)
    try:
        return int(data)
    except:
        try:
            return float(data)
        except:
            try:
                # see if can be coerced into datetime by first coercing to a string
                return make_serializable(dateutil.parse(unicode(data)))
            except:
                try:
                    # see if can be coerced into a dict (e.g. Dajngo Model or custom user module or class)
                    return make_serializable(data.__dict__)
                except:
                    # stringify it and give up
                    return unicode(data)