Самый простой способ сериализации объекта простого класса с simplejson?
Я пытаюсь сериализовать список объектов Python с помощью JSON (с использованием simplejson) и получаю сообщение об ошибке, что объект "не сериализуем JSON".
Класс является простым классом, имеющим поля, которые являются только целыми числами, строками и числами с плавающей точкой, и наследует аналогичные поля от одного родительского суперкласса, например:
class ParentClass:
def __init__(self, foo):
self.foo = foo
class ChildClass(ParentClass):
def __init__(self, foo, bar):
ParentClass.__init__(self, foo)
self.bar = bar
bar1 = ChildClass(my_foo, my_bar)
bar2 = ChildClass(my_foo, my_bar)
my_list_of_objects = [bar1, bar2]
simplejson.dump(my_list_of_objects, my_filename)
где foo, bar - простые типы, как я упоминал выше. Единственная хитрость в том, что у ChildClass иногда есть поле, которое ссылается на другой объект (типа, который не является ParentClass или ChildClass).
Какой самый простой способ сериализовать это как объект json с simplejson? Достаточно ли сделать его сериализуемым в виде словаря? Является ли лучший способ просто написать метод dict для ChildClass? Наконец, существенно ли усложняет наличие поля, которое ссылается на другой объект? Если это так, я могу переписать свой код, чтобы в классах были только простые поля (например, строки / числа с плавающей запятой и т. Д.)
благодарю вас.
7 ответов
Я использовал эту стратегию в прошлом и был ей очень доволен: закодируйте ваши пользовательские объекты как литералы объектов JSON (например, Python). dict
у) со следующей структурой:
{ '__ClassName__': { ... } }
Это по сути один предмет dict
чей единственный ключ является специальной строкой, которая определяет, какой тип объекта кодируется, а значение которого является dict
атрибутов экземпляра. Если это имеет смысл.
Очень простая реализация кодера и декодера (упрощенная из кода, который я фактически использовал) выглядит так:
TYPES = { 'ParentClass': ParentClass,
'ChildClass': ChildClass }
class CustomTypeEncoder(json.JSONEncoder):
"""A custom JSONEncoder class that knows how to encode core custom
objects.
Custom objects are encoded as JSON object literals (ie, dicts) with
one key, '__TypeName__' where 'TypeName' is the actual name of the
type to which the object belongs. That single key maps to another
object literal which is just the __dict__ of the object encoded."""
def default(self, obj):
if isinstance(obj, TYPES.values()):
key = '__%s__' % obj.__class__.__name__
return { key: obj.__dict__ }
return json.JSONEncoder.default(self, obj)
def CustomTypeDecoder(dct):
if len(dct) == 1:
type_name, value = dct.items()[0]
type_name = type_name.strip('_')
if type_name in TYPES:
return TYPES[type_name].from_dict(value)
return dct
В этой реализации предполагается, что объекты, которые вы кодируете, будут иметь from_dict()
метод класса, который знает, как воссоздать экземпляр из dict
декодируется из JSON.
Кодер и декодер легко расширить для поддержки пользовательских типов (например, datetime
объекты).
РЕДАКТИРОВАТЬ, чтобы ответить на ваше редактирование: Хорошая вещь о реализации, подобной этой, состоит в том, что она будет автоматически кодировать и декодировать экземпляры любого объекта, найденного в TYPES
отображение. Это означает, что он будет автоматически обрабатывать ChildClass следующим образом:
class ChildClass(object):
def __init__(self):
self.foo = 'foo'
self.bar = 1.1
self.parent = ParentClass(1)
Это должно привести к JSON что-то вроде следующего:
{ '__ChildClass__': {
'bar': 1.1,
'foo': 'foo',
'parent': {
'__ParentClass__': {
'foo': 1}
}
}
}
Экземпляр пользовательского класса может быть представлен в виде строки в формате JSON с помощью следующей функции:
def json_repr(obj):
"""Represent instance of a class as JSON.
Arguments:
obj -- any object
Return:
String that reprent JSON-encoded object.
"""
def serialize(obj):
"""Recursively walk object's hierarchy."""
if isinstance(obj, (bool, int, long, float, basestring)):
return obj
elif isinstance(obj, dict):
obj = obj.copy()
for key in obj:
obj[key] = serialize(obj[key])
return obj
elif isinstance(obj, list):
return [serialize(item) for item in obj]
elif isinstance(obj, tuple):
return tuple(serialize([item for item in obj]))
elif hasattr(obj, '__dict__'):
return serialize(obj.__dict__)
else:
return repr(obj) # Don't know how to handle, convert to string
return json.dumps(serialize(obj))
Эта функция создаст строку в формате JSON для
экземпляр пользовательского класса,
словарь, который имеет экземпляры пользовательских классов в виде листьев,
- список экземпляров пользовательских классов
Как указано в документации JSON для Python // help(json.dumps)
//>
Вы должны просто переопределить default()
метод JSONEncoder
чтобы обеспечить пользовательское преобразование типа и передать его как cls
аргумент.
Вот один, который я использую, чтобы покрыть специальные типы данных Mongo (datetime и ObjectId)
class MongoEncoder(json.JSONEncoder):
def default(self, v):
types = {
'ObjectId': lambda v: str(v),
'datetime': lambda v: v.isoformat()
}
vtype = type(v).__name__
if vtype in types:
return types[type(v).__name__](v)
else:
return json.JSONEncoder.default(self, v)
Называя это так просто, как
data = json.dumps(data, cls=MongoEncoder)
Если вы используете Django, это легко сделать с помощью модуля сериализаторов Django. Более подробную информацию можно найти здесь: https://docs.djangoproject.com/en/dev/topics/serialization/
У меня похожая проблема, но json.dump
функция не вызывается мной. Итак, чтобы сделать MyClass
Сериализация JSON без предоставления пользовательского кодераjson.dump
Вы должны Monkey патч кодировщик JSON.
Сначала создайте свой кодер в своем модуле my_module
:
import json
class JSONEncoder(json.JSONEncoder):
"""To make MyClass JSON serializable you have to Monkey patch the json
encoder with the following code:
>>> import json
>>> import my_module
>>> json.JSONEncoder.default = my_module.JSONEncoder.default
"""
def default(self, o):
"""For JSON serialization."""
if isinstance(o, MyClass):
return o.__repr__()
else:
return super(self,o)
class MyClass:
def __repr__(self):
return "my class representation"
Затем, как описано в комментарии, обезьяна исправит кодировщик json:
import json
import my_module
json.JSONEncoder.default = my_module.JSONEncoder.default
Теперь даже вызов json.dump
во внешней библиотеке (где вы не можете изменить cls
параметр) будет работать для вашего my_module.MyClass
объекты.
Это что-то вроде хакерства, и я уверен, что с этим может быть что-то не так. Однако я создавал простой сценарий и столкнулся с проблемой, заключающейся в том, что я не хотел создавать подкласс моего сериализатора json для сериализации списка объектов модели. Я закончил тем, что использовал понимание списка
Пусть: assets = список модельных объектов
Код:
myJson = json.dumps([x.__dict__ for x in assets])
Похоже, до сих пор работал очаровательно для моих нужд
Я чувствую себя немного глупо из-за моих возможных 2-х решений, перечитывающих его сейчас, конечно, когда вы используете django-rest-framework, у этого фреймворка есть несколько отличных возможностей, встроенных для этой проблемы, упомянутой выше.
посмотреть пример этой модели на их сайте
Если вы не используете django-rest-framework, это может помочь в любом случае:
На этой странице я нашел 2 полезных решения этой проблемы: (мне больше всего нравится второй!)
Возможное решение 1 (или путь): Дэвид Чамберс Дизайн сделал хорошее решение
Я надеюсь, что Дэвид не возражает, я скопировал и вставил его код решения здесь:
Определите метод сериализации для модели экземпляра:
def toJSON(self):
import simplejson
return simplejson.dumps(dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]]))
и он даже извлек метод выше, так что это более читабельно:
def toJSON(self):
fields = []
for field in self._meta.fields:
fields.append(field.name)
d = {}
for attr in fields:
d[attr] = getattr(self, attr)
import simplejson
return simplejson.dumps(d)
Пожалуйста, обратите внимание, это не мое решение, все кредиты идут по включенной ссылке. Просто подумал, что это должно быть переполнение стека.
Это также может быть реализовано в ответах выше.
Решение 2:
Мое предпочтительное решение найдено на этой странице:
http://www.traddicts.org/webdevelopment/flexible-and-simple-json-serialization-for-django/
Между прочим, я видел автора этого второго и лучшего решения: также на stack overflow:
Надеюсь, он это увидит, и мы можем поговорить о том, чтобы начать реализовывать и улучшать его код в открытом решении?