Serialización de un grupo nested nombrado en JSON con Python> = 2.7

Tengo un problema similar al problema de CalvinKrishy La solución de Samplebias no funciona con los datos que tengo.

Estoy usando Python 2.7.

Aquí están los datos:

Namedtuple

>>> a_t = namedtuple('a','f1 words') >>> word_t = namedtuple('word','f2 value') >>> w1 = word_t(f2=[0,1,2], value='abc') >>> w2 = word_t(f2=[3,4], value='def') >>> a1 = a_t(f1=[0,1,2,3,4],words=[w1, w2]) >>> a1 a(f1=[0, 1, 2, 3, 4], words=[word(f2=[0, 1, 2], value='abc'), word(f2=[3, 4], value='def')]) 

Dictado

 >>> w3 = {} >>> w3['f2'] = [0,1,2] >>> w3['value'] = 'abc' >>> w4 = {} >>> w4['f2'] = [3,4] >>> w4['value'] = 'def' >>> a2 = {} >>> a2['f1'] = [0, 1, 2, 3, 4] >>> a2['words'] = [w3,w4] >>> a2 {'f1': [0, 1, 2, 3, 4], 'words': [{'f2': [0, 1, 2], 'value': 'abc'}, {'f2': [3, 4], 'value': 'def'}]} 

Como puede ver, tanto a1 como a2 son iguales, excepto que uno tiene el nombre de tuple y el otro es dict .

Pero el json.dumps es diferente:

 >>> json.dumps(a1._asdict()) '{"f1": [0, 1, 2, 3, 4], "words": [[[0, 1, 2], "abc"], [[3, 4], "def"]]}' >>> json.dumps(a2) '{"f1": [0, 1, 2, 3, 4], "words": [{"f2": [0, 1, 2], "value": "abc"}, {"f2": [3, 4], "value": "def"}]}' 

Quiero tener el formato json de a1 exactamente como lo está haciendo para a2.

El problema está en el uso de namedtuple._asdict , no json.dumps . Si miras el código con namedtuple(..., verbose=True) verás esto:

 def _asdict(self): 'Return a new OrderedDict which maps field names to their values' return OrderedDict(zip(self._fields, self)) 

Solo el nivel superior se convierte realmente en un OrderedDict, todos los elementos contenidos se dejan intactos. Esto significa que las namedtuple anidadas aún son subclases de tuple y se serializan (correctamente) y namedtuple .

Si la llamada a una función de conversión específica es aceptable para usted (como la llamada a _asdict ), puede escribir su propia función.

 def namedtuple_asdict(obj): if hasattr(obj, "_asdict"): # detect namedtuple return OrderedDict(zip(obj._fields, (namedtuple_asdict(item) for item in obj))) elif isinstance(obj, basestring): # iterables - strings return obj elif hasattr(obj, "keys"): # iterables - mapping return OrderedDict(zip(obj.keys(), (namedtuple_asdict(item) for item in obj.values()))) elif hasattr(obj, "__iter__"): # iterables - sequence return type(obj)((namedtuple_asdict(item) for item in obj)) else: # non-iterable cannot contain namedtuples return obj json.dumps(namedtuple_asdict(a1)) # prints '{"f1": [0, 1, 2, 3, 4], "words": [{"f2": [0, 1, 2], "value": "abc"}, {"f2": [3, 4], "value": "def"}]}' 

Como puede ver, el problema más grande es tener estructuras anidadas que no tienen el namedtuple las namedtuple pero que podrían contenerlas.

Aquí está la versión con la que fui, adaptada de MisterMiyagi’s. Utilicé isinstance con collections.abc lugar de hasattr , e _type una clave _type en el dict resultante con el nombre de la clase nameduuple.

 import collections.abc def _nt_to_dict(obj): recurse = lambda x: map(_nt_to_dict, x) obj_is = lambda x: isinstance(obj, x) if obj_is(tuple) and hasattr(obj, '_fields'): # namedtuple fields = zip(obj._fields, recurse(obj)) class_name = obj.__class__.__name__ return dict(fields, **{'_type': class_name}) elif obj_is(collections.abc.Mapping): return type(obj)(zip(obj.keys(), recurse(obj.values()))) elif obj_is(collections.abc.Iterable) and not obj_is(str): return type(obj)(recurse(obj)) else: return obj