Самый простой способ сериализации объекта простого класса с 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:

selaux

Надеюсь, он это увидит, и мы можем поговорить о том, чтобы начать реализовывать и улучшать его код в открытом решении?

Другие вопросы по тегам