Самый простой способ сериализации объекта простого класса с simplejson? - PullRequest
28 голосов
/ 26 февраля 2010

Я пытаюсь сериализовать список объектов 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 ]

27 голосов
/ 26 февраля 2010

Я использовал эту стратегию в прошлом и был ей очень доволен: закодируйте ваши пользовательские объекты как литералы объектов 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 картографирование.Это означает, что он будет автоматически обрабатывать дочерний класс следующим образом:

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}
        }
    }
}
9 голосов
/ 13 января 2011

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

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

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

  • список экземпляров кастомов классы
2 голосов
/ 12 июня 2013

Как указано в документации 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)
2 голосов
/ 01 августа 2011

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

1 голос
/ 25 апреля 2018

У меня похожая проблема, но функция 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 объектов.

0 голосов
/ 11 июня 2014

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

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

Код:

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

Пока что, похоже, прекрасно работал для моих нужд

0 голосов
/ 15 августа 2012

Я чувствую себя немного глупо из-за моих двух возможных решений, перечитывающих его сейчас, конечно, когда вы используете 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/

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

Selaux

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

...