Создание сериализуемого объекта JSON с помощью обычного кодера
обычный способ JSON-сериализации пользовательских несериализуемых объектов-это подкласс json.JSONEncoder
и затем передать пользовательский кодировщик в отвалы.
обычно это выглядит так:
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)
то, что я пытаюсь сделать, это сделать что-то сериализуемое с помощью кодера по умолчанию. Я огляделся, но ничего не нашел.
Моя мысль заключается в том, что будет какое-то поле, в котором кодер смотрит на определение кодировки json. Что-то похожее на __str__
. Возможно __json__
поле.
Есть что-то подобное в Python?
Я хочу сделать один класс модуля, который я делаю, чтобы быть сериализуемым JSON для всех, кто использует пакет, не беспокоясь о реализации своих собственных [тривиальных] пользовательских кодеров.
5 ответов:
как я сказал в комментарии к вашему вопросу, посмотрев на
json
исходный код модуля, он, кажется, не поддается делать то, что вы хотите. Однако цель может быть достигнута тем, что известно как обезьяна-ямочный (см. вопрос что такое обезьяна патч?). Это может быть сделано в вашем пакете__init__.py
скрипт инициализации и повлияет на все последующиеjson
сериализация модулей, так как модули являются обычно загружается только один раз, и результат кэшируется вsys.modules
.патч изменяет кодер json по умолчанию
default
способ-по умолчаниюdefault()
.вот пример, реализованный как автономный модуль для простоты:
модуль:
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.
использование его тривиально, так как патч применяется путем простого импорта модуля.
пример клиента скрипт:
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\"}"
сохранить информация о типе объекта, специальный метод также может включать ее в возвращаемую строку:
return ('{"type": "%s", "name": "%s"}' % (self.__class__.__name__, self.name))
который производит следующий JSON, который теперь включает имя класса:
"{\"type\": \"Foo\", \"name\": \"sazpaz\"}"
даже лучше, чем замена
default()
ищите специально названный метод, чтобы он мог сериализовать большинство объектов Python автоматически, включая пользовательские экземпляры класса, без необходимости добавления специального метода. После исследования количество альтернатив, следующие из которых используетpickle
модуль, казалось, ближе всего к этому подходит ко мне:модуль:
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.
конечно, все не может быть замариновано-например, типы расширений. Однако есть способы, определенные для обработки их с помощью протокола pickle, написав специальные методы-подобные тому, что вы предложили, и я описал ранее,-но это, вероятно, будет необходимо для гораздо меньшего числа случаев.
независимо от, использование протокола pickle также означает, что было бы довольно легко восстановить исходный объект Python, предоставив пользовательский
object_hook
аргумент функции на любомjson.loads()
звонки, которые искали'_python_object'
ключ в переданном словаре. Что-то вроде: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)
если это должно быть сделано во многих местах, возможно, стоит определить функцию-оболочку, которая автоматически предоставляет дополнительный аргумент ключевого слова:
json_pkloads = functools.partial(json.loads, object_hook=as_python_object) pyobj = json_pkloads(json_str)
естественно, это может быть обезьяна-заделали его в
json
модуль, а также, что делает функцию по умолчаниюobject_hook
(вместоNone
).у меня есть идея для использования
pickle
С ответ by Компания Raymond Hettinger к другому вопросу сериализации JSON, который я считаю исключительно надежным, а также официальным источником (как в Python core developer).Portablity на Python 3
приведенный выше код не работает, как показано в Python 3, потому что
json.dumps()
возвращает
вы можете расширить класс dict следующим образом:
#!/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())
теперь, чтобы сделать ваши классы сериализуемыми с помощью обычного кодера, расширьте "сериализуемый":
class MySerializableClass(Serializable): attr_1 = 'first attribute' attr_2 = 23 def my_function(self): print('do something here') obj = MySerializableClass()
print(obj)
напечатает что-то вроде:<__main__.MySerializableClass object at 0x1073525e8>
print(json.dumps(obj, indent=4))
напечатает что-то вроде:{ "attr_1": "first attribute", "attr_2": 23, "my_function": "<bound method MySerializableClass.my_function of <__main__.MySerializableClass object at 0x1073525e8>>" }
Я предлагаю поместить Хак в определение класса. Таким образом, как только класс определен, он поддерживает JSON. Пример:
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 ]
проблема с переопределением
JSONEncoder().default
это то, что вы можете сделать это только один раз. Если вы наткнетесь на что-нибудь специальный тип данных, который не работает с этим шаблоном (например, если вы используете странную кодировку). С помощью приведенного ниже шаблона вы всегда можете сделать свой класс JSON сериализуемым, при условии, что поле класса, которое вы хотите сериализовать, само сериализуется (и может быть добавлено в список python, почти ничего). В противном случае вы должны рекурсивно применить тот же шаблон к своему полю json (или извлечь сериализуемые данные из него):# 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)
Я не понимаю, почему вы не можете написать
serialize
функция для вашего собственного класса? Вы реализуете пользовательский кодер внутри самого класса и позволяете "людям" вызывать функцию serialize, которая по существу возвращаетself.__dict__
с удаленными функциями.edit:
этот вопрос согласен со мной, что самый простой способ-написать свой собственный метод и вернуть сериализованные данные json, которые вы хотите. Они также рекомендуют попробовать jsonpickle, но теперь вы добавляете дополнительную зависимость для красоты, когда правильное решение встроено.