Q:

How do you enable python's json package to encode attrdict.AttrDict objects?

How do you encode AttrDict objects in json?
import sys, json, attrdict

ad = attrdict.AttrDict({'else': 1, 'inner': attrdict.AttrDict({'something': 2})})

json.dump(ad, sys.stdout)

This fails with TypeError: a{'something': 2} is not JSON serializable
Using a custom encoder like this works but I have to reference the private _mapping property:
import sys, json, attrdict

class attrDictEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, attrdict.AttrDict):
            return obj._mapping
        return json.JSONEncoder.default(self, obj)

ad = attrdict.AttrDict({'else': 1, 'inner': attrdict.AttrDict({'something': 2})})

json.dump(ad, sys.stdout, cls=attrDictEncoder)

is there a better/cleaner way? I don't want to have to rebuild the dicts, item by item into a plain dicts.

A:

It's possible to fool json into believing it is handling a real dict by subclassing dict and then monkey patching the methods that json uses to create the json object string. This way we can give json a dummy class that just calls the relevant methods of the given AttrDict.
def as_attrdict(val):
    if not isinstance(val, AttrDict):
        raise TypeError('not AttrDict')
    return AttrDictForJson(val)

class AttrDictForJson(dict):

    def __init__(self, attrdict):
        super().__init__()
        self.items = attrdict.items
        self._len = attrdict.__len__
        # key creation necessary for json.dump to work with CPython 
        # This is because optimised json bypasses __len__ on CPython
        if self._len() != 0:
            self[None] = None

    def __len__(self):
        return self._len()

Usage:
json_string = dumps(attrdict, default=as_attrdict)

I've tested this on python 3.4, if you have a different version of python then the above may require some tweaking, such as changing attrdict.items to attrdict.iteritems

