Сериализовать несколько классов до JSON - PullRequest
2 голосов
/ 11 июля 2020

У меня есть класс A, в котором хранится набор переменных типа B, как я могу правильно сериализовать класс A в JSON?

пример:

class A:
    def __init__(self):
        self.b_collection = []
    #...

class B:
    def __init__(self):
        # ...
        pass

и добавить экземпляры B в коллекцию:

a = A()
a.b_collection = [B(), B(), B()]

, когда я пытаюсь сериализовать a с помощью json.dumps(a), я получаю эту ошибку: Object of type A is not JSON serializable.

Есть ли способ укажите, как кодировщик должен кодировать этот класс?

что-то вроде

def __encode__(self, encoder):
    encoder.start_obj()
    encoder.add_property('name', self.value)
    encoder.add_property('age', self.age)
    encoder.end_obj()

, которое вернет что-то вроде

{
   name: 'Tomer',
   age: '19'
}

Ответы [ 2 ]

3 голосов
/ 11 июля 2020

Вы можете расширить json.JSONEncoder, чтобы определить, как сериализовать ваши объекты. Метод default вашего подкласса примет в качестве аргумента объект Python. Вы можете вернуть новый объект, который (надеюсь) кодируемый, или передать объект родительскому объекту в надежде, что он знает, как кодировать объект.

Например,

class A:
    def __init__(self):
        self.b_collection = []

class B:
    def __init__(self, name, age):
        self.name = name
        self.age = age


class ABEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, A):
            return {'__A__': obj.b_collection}
        elif isinstance(obj, B):
            return {'__B__': obj.__dict__}
        return super().default(obj)


a = A()
a.b_collection.append(B("Tomer", "19"))
a.b_collection.append(B("Bob", "21"))
a.b_collection.append(B("Alice", "23"))
print(json.dumps(a, cls=ABEncoder, indent=4))

выдаст

{
    "__A__": [
        {
            "__B__": {
                "name": "Tomer",
                "age": "19"
            }
        },
        {
            "__B__": {
                "name": "Bob",
                "age": "21"
            }
        },
        {
            "__B__": {
                "name": "Alice",
                "age": "23"
            }
        }
    ]
}

Обратите внимание, что вы можете обрабатывать A и B отдельно; вам не нужно сначала кодировать объекты B перед возвратом кодируемой формы A; объекты B будут закодированы позже, когда будет закодирован сам список.

Дополнительные объекты облегчают написание декодера; вам не нужно усложнять его, если вы не хотите иметь возможность декодировать JSON в экземпляр A. Вместо этого вы можете просто определить

class ABEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, A):
            return obj.b_collection
        elif isinstance(obj, B):
            return obj.__dict__
        return super().default(obj)

, чтобы получить

[
    {
        "name": "Tomer",
        "age": "19"
    },
    {
        "name": "Bob",
        "age": "21"
    },
    {
        "name": "Alice",
        "age": "23"
    }
]
    
0 голосов
/ 13 июля 2020

Может быть, классы данных могут помочь:

from dataclasses import asdict, dataclass
from typing import List
import json


@dataclass
class Person:
    name: str
    age: str


@dataclass
class Group:
    persons: List[Person]


data = [
    {'name': 'Tomer', 'age': '19'},
    {'name': 'Ivan', 'age': '20'}
]
persons = [Person(**person) for person in data]

group = Group(persons=persons)
assert json.dumps(asdict(group)) == '{"persons": [{"name": "Tomer", "age": "19"}, {"name": "Ivan", "age": "20"}]}'

persons = [asdict(person) for person in group.persons]
assert persons == [{"name": "Tomer", "age": "19"}, {"name": "Ivan", "age": "20"}]
assert json.dumps(persons) == '[{"name": "Tomer", "age": "19"}, {"name": "Ivan", "age": "20"}]'

...