Самый простой способ сериализации простого объекта класса с 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? Наконец, имеет ли поле, которое относится к другому объекту, значительно усложняет ситуацию? Если это так, я могу переписать мой код, чтобы иметь только простые поля в классах (например, строки /float и т.д.).

спасибо.

Ответ 1

Я использовал эту стратегию в прошлом и был очень доволен ею: кодируйте свои пользовательские объекты как литералы объектов JSON (например, Python dict s) со следующей структурой:

{ '__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}
        }
    }
}

Ответ 2

Экземпляр настраиваемого класса может быть представлен как форматированная строка 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 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 для

  • экземпляр пользовательского класса,

  • словарь, который имеет экземпляры пользовательские классы как листья,

  • список экземпляров пользовательских классы

Ответ 3

Если вы используете Django, его можно легко выполнить с помощью модуля Django serializers. Более подробную информацию можно найти здесь: https://docs.djangoproject.com/en/dev/topics/serialization/

Ответ 4

Как указано в python JSON docs// 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)

Ответ 5

Мне немного глупо о моих возможных 2 решениях, перечитывающих его сейчас, конечно, когда вы используете django-rest-framework, эта структура имеет несколько отличных функций для этой проблемы, упомянутой выше.

см. этот пример представления модели на своем веб-сайте

Если вы не используете django-rest-framework, это может помочь в любом случае:

Я нашел 2 полезные решения для этой проблемы на этой странице: (Мне нравится второй самый!)

Возможное решение 1 (или способ перехода): David Chambers Design сделал приятное решение

Надеюсь, Дэвид не против, я скопирую его код решения здесь:

Определите метод сериализации в модели экземпляра:

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/

Кстати, я видел автора этого второго и лучшего решения: также на stackoverflow:

Selaux

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

Ответ 6

Это своего рода хакерство, и я уверен, что, вероятно, это может быть неправильно. Тем не менее, я создавал простой script, и я запускал проблему, из-за которой я не хотел подклассифицировать сериализатор json для сериализации списка объектов модели. Я закончил использовать понимание списка

Пусть: assets = список модельных объектов

код:

myJson = json.dumps([x.__dict__ for x in assets])

До сих пор, кажется, работало очаровательно для моих потребностей