web-dev-qa-db-fra.com

Sérialisation d'un fichier Python nommé en JSON

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?

62
calvinkrishy

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"}}
46
samplebias

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}'
51
benselme

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.

20
singingwolfboy

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.

3
LtWorf

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'}
1
Tolgahan ÜZÜN

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))
1
Dmitry T.

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.

0
Gonzalo

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.

0
dlamblin