Rendre l'objet JSON sérialisable avec un encodeur régulier
la méthode habituelle de JSON-sérializing custom Non-serializable objects consiste à utiliser la sous-classe json.JSONEncoder
puis à passer un encodeur custom à dumps.
il ressemble habituellement à ceci:
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, foo):
return obj.to_json()
return json.JSONEncoder.default(self, obj)
print json.dumps(obj, cls = CustomEncoder)
ce que j'essaie de faire, c'est de rendre quelque chose sérialisable avec l'encodeur par défaut. J'ai cherché partout, mais je n'ai rien trouvé.
Ma pensée est qu'il y aurait un domaine dans lequel le codeur regarde pour déterminer l'encodage json. Quelque similaire à __str__
. Peut-être un champ __json__
.
Il y a quelque chose comme ça en python?
je veux faire une classe d'un module que je fais pour être sérialisable JSON à tous ceux qui utilisent le paquet sans se soucier de mettre en œuvre leurs propres encodeurs personnalisés [trivial].
5 réponses
comme je l'ai dit dans un commentaire à votre question, après avoir regardé le code source du module json
, il ne semble pas se prêter à faire ce que vous voulez. Toutefois, l'objectif pourrait être atteint par ce qui est connu sous le nom de "patching
(voir la question Qu'est-ce qu'un patch de singe? ).
Cela pourrait être fait dans le script d'initialisation __init__.py
de votre paquet et affecterait tous les json
module de sérialisation étant donné que les modules sont généralement seulement chargé une fois et le résultat est mis en cache dans sys.modules
.
le correctif modifie la méthode par défaut de l'encodeur json default
-la méthode par défaut default()
.
voici un exemple implémenté comme un module autonome pour simplifier:
Module: make_json_serializable.py
""" Module that monkey-patches json module when it's imported so
JSONEncoder.default() automatically checks for a special "to_json()"
method and uses it to encode the object if found.
"""
from json import JSONEncoder
def _default(self, obj):
return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder.default # Save unmodified default.
JSONEncoder.default = _default # Replace it.
son utilisation est triviale puisque le patch est appliqué par simplement importer le module.
exemple de script client:
import json
import make_json_serializable # apply monkey-patch
class Foo(object):
def __init__(self, name):
self.name = name
def to_json(self): # New special method.
""" Convert to JSON format string representation. """
return '{"name": "%s"}' % self.name
foo = Foo('sazpaz')
print(json.dumps(foo)) # -> "{\"name\": \"sazpaz\"}"
pour conserver l'information de type d'objet, la méthode spéciale peut également l'inclure dans la chaîne retournée:
return ('{"type": "%s", "name": "%s"}' %
(self.__class__.__name__, self.name))
qui produit le JSON suivant qui comprend maintenant le nom de classe:
"{\"type\": \"Foo\", \"name\": \"sazpaz\"}"
encore mieux que d'avoir le remplacement default()
chercher une méthode spécialement nommée, serait pour lui d'être en mesure pour sérialiser la plupart des objets Python automatiquement , y compris les instances de classe définies par l'utilisateur, sans avoir besoin d'ajouter une méthode spéciale. Après avoir recherché un certain nombre d'alternatives, ce qui suit qui utilise le module pickle
, a semblé le plus proche de cet idéal pour moi:
Module: make_json_serializable2.py
""" Module that imports the json module and monkey-patches it so
JSONEncoder.default() automatically pickles any Python objects
encountered that aren't standard JSON data types.
"""
from json import JSONEncoder
import pickle
def _default(self, obj):
return {'_python_object': pickle.dumps(obj)}
JSONEncoder.default = _default # Replace with the above.
bien sûr tout ne peut pas être décapé-les types d'extension par exemple. Cependant il y a des moyens définis pour les gérer par le biais du protocole pickle en écrivant des méthodes spéciales-semblables à ce que vous avez suggéré et que j'ai décrit plus tôt-mais faire cela serait probablement nécessaire pour un nombre beaucoup moins élevé de cas.
quoi qu'il en soit, l'utilisation du protocole pickle signifie également qu'il serait assez facile de reconstruire l'objet Python original en fournissant un argument de fonction personnalisé object_hook
sur tous les appels json.loads()
qui cherchaient une touche '_python_object'
dans le dictionnaire passé. Quelque chose comme:
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(str(dct['_python_object']))
return dct
pyobj = json.loads(json_str, object_hook=as_python_object)
si cela doit être fait dans de nombreux endroits, il pourrait être utile de définir une fonction d'enrubannage qui a fourni automatiquement l'argument de mot-clé supplémentaire:
json_pkloads = functools.partial(json.loads, object_hook=as_python_object)
pyobj = json_pkloads(json_str)
naturellement, cela pourrait être patché en singe dans le module json
ainsi, ce qui rend la fonction par défaut object_hook
(au lieu de None
).
j'ai eu l'idée d'utiliser pickle
à partir d'une réponse par Raymond Hettinger à l'autre, la sérialisation JSON question, que je considère comme exceptionnellement crédible ainsi qu'une source officielle (comme en Python core developer).
portabilité à Python 3
le code ci-dessus ne fonctionne pas comme indiqué dans Python 3 parce que json.dumps()
renvoie un bytes
objet que le JSONEncoder
ne peut pas gérer. Toutefois, cette approche est toujours valable. Une façon simple de contourner le l'édition est à latin1
"décoder" la valeur retournée de pickle.dumps()
et puis "l'encoder" de latin1
avant de la transmettre à pickle.loads()
dans la fonction as_python_object()
. Cela fonctionne parce que les chaînes binaires arbitraires sont valides latin1
qui peut toujours être décodé en Unicode et puis encodé à nouveau à la chaîne originale (comme indiqué dans cette réponse par Sven Marnach ).
(Bien que ce qui suit fonctionne bien en Python 2, le latin1
décodant et encodant il fait est superflu.)
from decimal import Decimal
class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj):
return {'_python_object': pickle.dumps(obj).decode('latin1')}
def as_python_object(dct):
if '_python_object' in dct:
return pickle.loads(dct['_python_object'].encode('latin1'))
return dct
data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'},
Decimal('3.14')]
j = json.dumps(data, cls=PythonObjectEncoder, indent=4)
data2 = json.loads(j, object_hook=as_python_object)
assert data == data2 # both should be same
vous pouvez étendre la classe DCT comme suit:
#!/usr/local/bin/python3
import json
class Serializable(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hack to fix _json.so make_encoder serialize properly
self.__setitem__('dummy', 1)
def _myattrs(self):
return [
(x, self._repr(getattr(self, x)))
for x in self.__dir__()
if x not in Serializable().__dir__()
]
def _repr(self, value):
if isinstance(value, (str, int, float, list, tuple, dict)):
return value
else:
return repr(value)
def __repr__(self):
return '<%s.%s object at %s>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self))
)
def keys(self):
return iter([x[0] for x in self._myattrs()])
def values(self):
return iter([x[1] for x in self._myattrs()])
def items(self):
return iter(self._myattrs())
maintenant pour rendre vos classes sérialisables avec l'encodeur régulier, étendre 'sérialisable':
class MySerializableClass(Serializable):
attr_1 = 'first attribute'
attr_2 = 23
def my_function(self):
print('do something here')
obj = MySerializableClass()
print(obj)
imprimera quelque chose comme:
<__main__.MySerializableClass object at 0x1073525e8>
print(json.dumps(obj, indent=4))
imprimera quelque chose comme:
{
"attr_1": "first attribute",
"attr_2": 23,
"my_function": "<bound method MySerializableClass.my_function of <__main__.MySerializableClass object at 0x1073525e8>>"
}
je suggère de mettre le hack dans la définition de classe. De cette façon, une fois que la classe est définie, elle supporte JSON. Exemple:
import json
class MyClass( object ):
def _jsonSupport( *args ):
def default( self, xObject ):
return { 'type': 'MyClass', 'name': xObject.name() }
def objectHook( obj ):
if 'type' not in obj:
return obj
if obj[ 'type' ] != 'MyClass':
return obj
return MyClass( obj[ 'name' ] )
json.JSONEncoder.default = default
json._default_decoder = json.JSONDecoder( object_hook = objectHook )
_jsonSupport()
def __init__( self, name ):
self._name = name
def name( self ):
return self._name
def __repr__( self ):
return '<MyClass(name=%s)>' % self._name
myObject = MyClass( 'Magneto' )
jsonString = json.dumps( [ myObject, 'some', { 'other': 'objects' } ] )
print "json representation:", jsonString
decoded = json.loads( jsonString )
print "after decoding, our object is the first in the list", decoded[ 0 ]
le problème avec JSONEncoder().default
est que vous ne pouvez le faire qu'une seule fois. Si vous tombez sur quelque chose d'un type de données spécial qui ne fonctionne pas avec ce modèle (comme si vous utilisez un étrange encodage). Avec le modèle ci-dessous, vous pouvez toujours rendre votre classe JSON sérialisable, à condition que le champ de classe que vous voulez sérialiser soit lui-même sérialisable (et peut être ajouté à une liste python, à peine n'importe quoi). Sinon, vous devez appliquer récursivement le même modèle de votre champ json (ou extraire les données sérialisables de celui-ci):
# base class that will make all derivatives JSON serializable:
class JSONSerializable(list): # need to derive from a serializable class.
def __init__(self, value = None):
self = [ value ]
def setJSONSerializableValue(self, value):
self = [ value ]
def getJSONSerializableValue(self):
return self[1] if len(self) else None
# derive your classes from JSONSerializable:
class MyJSONSerializableObject(JSONSerializable):
def __init__(self): # or any other function
# ....
# suppose your__json__field is the class member to be serialized.
# it has to be serializable itself.
# Every time you want to set it, call this function:
self.setJSONSerializableValue(your__json__field)
# ...
# ... and when you need access to it, get this way:
do_something_with_your__json__field(self.getJSONSerializableValue())
# now you have a JSON default-serializable class:
a = MyJSONSerializableObject()
print json.dumps(a)
Je ne comprends pas pourquoi vous ne pouvez pas écrire une fonction serialize
pour votre propre classe? Vous implémentez l'encodeur personnalisé à l'intérieur de la classe elle-même et permettez à "people" d'appeler la fonction serialize qui retournera essentiellement self.__dict__
avec des fonctions supprimées.
edit:
Cette question d'accord avec moi, que le moyen le plus simple est d'écrire votre propre méthode et retour le json de données sérialisées que vous voulez. Ils nous vous recommandons également d'essayer jsonpickle, mais maintenant vous ajoutez une dépendance supplémentaire pour la beauté lorsque la bonne solution est intégrée.