Quelle est la méthode recommandée pour sérialiser un namedtuple
to json avec les noms de champs conservés?
La sérialisation d'une namedtuple
à json entraîne uniquement la sérialisation des valeurs et la perte des noms de champs dans la traduction. Je voudrais que les champs soient également conservés lorsque json-ized et a donc fait ce qui suit:
class foobar(namedtuple('f', 'foo, bar')):
__slots__ = ()
def __iter__(self):
yield self._asdict()
Ce qui précède est sérialisé en json comme je l’attendais et se comporte comme namedtuple
dans d’autres endroits que j’utilise (accès aux attributs, etc.), sauf avec un résultat autre que Tuple lorsqu’il est itéré (ce qui convient à mon cas d’utilisation).
Quelle est la "manière correcte" de convertir en json avec les noms de champs conservés?
C'est assez compliqué, car namedtuple()
est une fabrique qui renvoie un nouveau type dérivé de Tuple
. Une approche serait que votre classe hérite également de UserDict.DictMixin
, mais Tuple.__getitem__
est déjà défini et attend un entier indiquant la position de l'élément, et non le nom de son attribut:
>>> f = foobar('a', 1)
>>> f[0]
'a'
En son cœur, la syntaxe nommée est une solution étrange pour JSON, car il s’agit bien d’un type/ construit sur mesure dont les noms de clé sont définis dans la définition du type , contrairement à un dictionnaire dans lequel les noms de clé sont stockés dans l’instance. Cela vous empêche de "faire le tour" d'un nom nommé, par exemple. vous ne pouvez pas décoder un dictionnaire en un fichier nommé sans un autre élément d'information, comme un marqueur de type spécifique à une application dans dict {'a': 1, '#_type': 'foobar'}
, qui est un peu bidon.
Ce n’est pas idéal, mais si vous avez seulement besoin d’encoder namedtuples dans des dictionnaires, une autre approche consiste à étendre ou modifier votre encodeur JSON en fonction de ces types. Voici un exemple de sous-classement du json.JSONEncoder
Python. Cela résout le problème de la conversion correcte des dictionnaire nommés imbriqués en dictionnaires:
from collections import namedtuple
from json import JSONEncoder
class MyEncoder(JSONEncoder):
def _iterencode(self, obj, markers=None):
if isinstance(obj, Tuple) and hasattr(obj, '_asdict'):
gen = self._iterencode_dict(obj._asdict(), markers)
else:
gen = JSONEncoder._iterencode(self, obj, markers)
for chunk in gen:
yield chunk
class foobar(namedtuple('f', 'foo, bar')):
pass
enc = MyEncoder()
for obj in (foobar('a', 1), ('a', 1), {'outer': foobar('x', 'y')}):
print enc.encode(obj)
{"foo": "a", "bar": 1}
["a", 1]
{"outer": {"foo": "x", "bar": "y"}}
Si vous cherchez à sérialiser namedtuple
, vous utiliserez sa méthode _asdict()
(avec Python> = 2.7).
>>> from collections import namedtuple
>>> import json
>>> FB = namedtuple("FB", ("foo", "bar"))
>>> fb = FB(123, 456)
>>> json.dumps(fb._asdict())
'{"foo": 123, "bar": 456}'
Il semblerait que vous ayez l'habitude de sous-classer simplejson.JSONEncoder
pour que cela fonctionne, mais avec le dernier code simplejson, ce n'est plus le cas: vous devez réellement modifier le code du projet. Je ne vois aucune raison pour que simplejson ne prenne pas en charge les échantillons nommés. J'ai donc créé le projet, ajouté le support namedtuple, et je suis { j'attends actuellement le retour de ma branche dans le projet principal }. Si vous avez besoin des correctifs maintenant, tirez simplement de ma fourchette.
EDIT: les versions les plus récentes de simplejson
prennent désormais cela en charge de manière native avec l'option namedtuple_as_object
, dont la valeur par défaut est True
.
J'ai écrit une bibliothèque pour cela: https://github.com/ltworf/typedload
Il peut aller de et vers nommé-Tuple et revenir.
Il supporte des structures imbriquées assez compliquées, avec des listes, des ensembles, des énumérations, des unions, des valeurs par défaut. Il devrait couvrir les cas les plus courants.
Il convertit de manière récursive les données namedTuple en json.
print(m1)
## Message(id=2, agent=Agent(id=1, first_name='asd', last_name='asd', mail='[email protected]'), customer=Customer(id=1, first_name='asd', last_name='asd', mail='[email protected]', phone_number=123123), type='image', content='text', media_url='h.com', la=123123, ls=4512313)
def reqursive_to_json(obj):
_json = {}
if isinstance(obj, Tuple):
datas = obj._asdict()
for data in datas:
if isinstance(datas[data], Tuple):
_json[data] = (reqursive_to_json(datas[data]))
else:
print(datas[data])
_json[data] = (datas[data])
return _json
data = reqursive_to_json(m1)
print(data)
{'agent': {'first_name': 'asd',
'last_name': 'asd',
'mail': '[email protected]',
'id': 1},
'content': 'text',
'customer': {'first_name': 'asd',
'last_name': 'asd',
'mail': '[email protected]',
'phone_number': 123123,
'id': 1},
'id': 2,
'la': 123123,
'ls': 4512313,
'media_url': 'h.com',
'type': 'image'}
Il existe une solution plus pratique consiste à utiliser le décorateur (il utilise le champ protégé _fields
).
Python 2.7+:
import json
from collections import namedtuple, OrderedDict
def json_serializable(cls):
def as_dict(self):
yield OrderedDict(
(name, value) for name, value in Zip(
self._fields,
iter(super(cls, self).__iter__())))
cls.__iter__ = as_dict
return cls
#Usage:
C = json_serializable(namedtuple('C', 'a b c'))
print json.dumps(C('abc', True, 3.14))
# or
@json_serializable
class D(namedtuple('D', 'a b c')):
pass
print json.dumps(D('abc', True, 3.14))
Python 3.6.6+:
import json
from typing import TupleName
def json_serializable(cls):
def as_dict(self):
yield {name: value for name, value in Zip(
self._fields,
iter(super(cls, self).__iter__()))}
cls.__iter__ = as_dict
return cls
# Usage:
@json_serializable
class C(NamedTuple):
a: str
b: bool
c: float
print(json.dumps(C('abc', True, 3.14))
La bibliothèque jsonplus fournit un sérialiseur pour les instances NamedTuple. Utilisez son mode de compatibilité pour générer des objets simples si nécessaire, mais préférez le paramètre par défaut, qui facilite le décodage.
C'est une vieille question. Toutefois:
Une suggestion pour tous ceux qui ont la même question: réfléchissez bien à l’utilisation des fonctionnalités privées ou internes de NamedTuple
car elles l’ont déjà fait et changeront à nouveau avec le temps.
Par exemple, si votre NamedTuple
est un objet à valeur fixe et que vous souhaitez uniquement le sérialiser, et non dans les cas où il est imbriqué dans un autre objet, vous pouvez éviter les problèmes qui entraîneraient la suppression de __dict__
ou le changement de _as_dict()
. faites quelque chose comme (et oui c'est Python 3 parce que cette réponse est pour le moment):
from typing import NamedTuple
class ApiListRequest(NamedTuple):
group: str="default"
filter: str="*"
def to_dict(self):
return {
'group': self.group,
'filter': self.filter,
}
def to_json(self):
return json.dumps(self.to_dict())
J'ai essayé d'utiliser le default
callable kwarg à dumps
afin de faire l'appel to_dict()
s'il est disponible, mais cela n'a pas été appelé car le NamedTuple
est convertible en liste.