JSON conversion function: a 'visitor' function which implements marshall
    out (to JSON data form), honoring JSON property types/hints but does not
    require them.  To convert to an actual JSON document, pass the return value
    to ``json.dumps`` or a similar function.

    args:
        ``record=``\ *anything*
            This object can be of any type; a best-effort attempt is made to
            convert to a form which ``json.dumps`` can accept; this function
            will call itself recursively, respecting any types which define
            ``.json_data()`` as a method and calling that.

        ``extraneous=``\ *BOOL*
            This parameter is passed through to any ``json_data()`` methods
            which support it.

        ``prop=``\ *PROPNAME*\ |\ *PROPERTY*
            Specifies to return the given property from an object, calling any
            ``to_json`` mapping defined on the property.  Does not catch the
            ``AttributeError`` that is raised by the property not being set.

def to_json(record, extraneous=True, prop=None):
    """JSON conversion function: a 'visitor' function which implements marshall
    out (to JSON data form), honoring JSON property types/hints but does not
    require them.  To convert to an actual JSON document, pass the return value
    to ``json.dumps`` or a similar function.

    args:
        ``record=``\ *anything*
            This object can be of any type; a best-effort attempt is made to
            convert to a form which ``json.dumps`` can accept; this function
            will call itself recursively, respecting any types which define
            ``.json_data()`` as a method and calling that.

        ``extraneous=``\ *BOOL*
            This parameter is passed through to any ``json_data()`` methods
            which support it.

        ``prop=``\ *PROPNAME*\ |\ *PROPERTY*
            Specifies to return the given property from an object, calling any
            ``to_json`` mapping defined on the property.  Does not catch the
            ``AttributeError`` that is raised by the property not being set.
    """
    if prop:
        if isinstance(prop, basestring):
            prop = type(record).properties[prop]
        val = prop.__get__(record)
        if hasattr(prop, "to_json"):
            return prop.to_json(val, extraneous, _json_data)
        else:
            return _json_data(val, extraneous)

    elif isinstance(record, Collection):
        if isinstance(record, RecordDict):
            return dict(
                (k, _json_data(v, extraneous)) for k, v in record.items()
            )
        else:
            return list(_json_data(x, extraneous) for x in record)

    elif isinstance(record, Record):
        rv_dict = {}
        for propname, prop in type(record).properties.iteritems():
            if not extraneous and prop.extraneous:
                pass
            elif prop.slot_is_empty(record):
                pass
            elif not hasattr(prop, "json_name") or prop.json_name is not None:
                json_name = getattr(prop, "json_name", prop.name)
                try:
                    rv_dict[json_name] = to_json(record, extraneous, prop)
                except AttributeError:
                    pass
        return rv_dict

    elif isinstance(record, long):
        return str(record) if abs(record) > 2**50 else record

    elif isinstance(record, dict):
        return dict(
            (k, _json_data(v, extraneous)) for k, v in record.iteritems()
        )

    elif isinstance(record, (list, tuple, set, frozenset)):
        return list(_json_data(x, extraneous) for x in record)

    elif isinstance(record, (basestring, int, float, types.NoneType)):
        return record

    else:
        raise TypeError(
            "I don't know how to marshall a %s to JSON" %
            type(record).__name__
        )