Как преобразовать данные JSON в объект Python

Я хочу использовать Python для преобразования данных JSON в объект Python.

Я получаю объекты данных JSON из API Facebook, которые я хочу сохранить в своей базе данных.

Мой текущий вид в Django (Python) (request.POST содержит JSON):

response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
  • Это прекрасно работает, но как мне обрабатывать сложные объекты данных JSON?

  • Разве не было бы намного лучше, если бы я мог как-то преобразовать этот объект JSON в объект Python для простоты использования?

33 ответа

Решение

Вы можете сделать это в одну строку, используя namedtuple а также object_hook:

import json
from collections import namedtuple

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

# Parse JSON into an object with attributes corresponding to dict keys.
x = json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
print x.name, x.hometown.name, x.hometown.id

или, чтобы использовать это легко:

def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def json2obj(data): return json.loads(data, object_hook=_json_object_hook)

x = json2obj(data)

Если вы хотите, чтобы он обрабатывал ключи, которые не являются хорошими именами атрибутов, посмотрите namedtuple"s rename параметр.

Вы можете попробовать это:

class User(object):
    def __init__(self, name, username, *args, **kwargs):
        self.name = name
        self.username = username

import json
j = json.loads(your_json)
u = User(**j)

Просто создайте новый объект и передайте параметры в виде карты.

Проверьте раздел, озаглавленный Специализация декодирования объектов JSON в json модульная документация. Вы можете использовать это для декодирования объекта JSON в определенный тип Python.

Вот пример:

class User(object):
    def __init__(self, name, username):
        self.name = name
        self.username = username

import json
def object_decoder(obj):
    if '__type__' in obj and obj['__type__'] == 'User':
        return User(obj['name'], obj['username'])
    return obj

json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}',
           object_hook=object_decoder)

print type(User)  # -> <type 'type'>

Обновить

Если вы хотите получить доступ к данным в словаре через модуль json, сделайте это:

user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}')
print user['name']
print user['username']

Так же, как обычный словарь.

Это не код гольф, но вот мой самый короткий трюк, используя types.SimpleNamespace в качестве контейнера для объектов JSON.

По сравнению с ведущими namedtuple Решение, это:

  • возможно быстрее / меньше, так как не создает класс для каждого объекта
  • короче
  • нет rename вариант, и, вероятно, такое же ограничение на ключи, которые не являются действительными идентификаторами (использует setattr под одеялом)

Пример:

from __future__ import print_function
import json

try:
    from types import SimpleNamespace as Namespace
except ImportError:
    # Python 2.x fallback
    from argparse import Namespace

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

x = json.loads(data, object_hook=lambda d: Namespace(**d))

print (x.name, x.hometown.name, x.hometown.id)

Вот быстрая и грязная альтернатива JSON Pickle

import json

class User:
    def __init__(self, name, username):
        self.name = name
        self.username = username

    def to_json(self):
        return json.dumps(self.__dict__)

    @classmethod
    def from_json(cls, json_str):
        json_dict = json.loads(json_str)
        return cls(**json_dict)

# example usage
User("tbrown", "Tom Brown").to_json()
User.from_json(User("tbrown", "Tom Brown").to_json()).to_json()

Для сложных объектов вы можете использовать JSON Pickle

Библиотека Python для сериализации любого произвольного графа объектов в JSON. Он может взять практически любой объект Python и превратить его в JSON. Кроме того, он может восстановить объект обратно в Python.

Если вы используете Python 3.5+, вы можете использовать jsons чтобы сериализовать и десериализовать в простые старые объекты Python:

import jsons

response = request.POST

# You'll need your class attributes to match your dict keys, so in your case do:
response['id'] = response.pop('user_id')

# Then you can load that dict into your class:
user = jsons.load(response, FbApiUser)

user.save()

Вы также можете сделать FbApiUser наследовать от jsons.JsonSerializable для большей элегантности:

user = FbApiUser.from_json(response)

Эти примеры будут работать, если ваш класс состоит из типов Python по умолчанию, таких как строки, целые числа, списки, даты и т. Д. jsons lib будет требовать подсказки типов для пользовательских типов.

Если вы используете Python 3.6+, вы можете использовать https://pypi.org/project/marshmallow-dataclass/. Вопреки всем решениям, перечисленным выше, он прост и безопасен:

from marshmallow_dataclass import dataclass

@dataclass
class User:
    name: str

user, err = User.Schema().load({"name": "Ramirez"})

Поскольку никто не дал такого ответа, как я, я опубликую его здесь.

Это надежный класс, который может легко конвертировать между JSON str а также dict что я скопировал из моего ответа на другой вопрос:

import json

class PyJSON(object):
    def __init__(self, d):
        if type(d) is str:
            d = json.loads(d)

        self.from_dict(d)

    def from_dict(self, d):
        self.__dict__ = {}
        for key, value in d.items():
            if type(value) is dict:
                value = PyJSON(value)
            self.__dict__[key] = value

    def to_dict(self):
        d = {}
        for key, value in self.__dict__.items():
            if type(value) is PyJSON:
                value = value.to_dict()
            d[key] = value
        return d

    def __repr__(self):
        return str(self.to_dict())

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __getitem__(self, key):
        return self.__dict__[key]

json_str = """... json string ..."""

py_json = PyJSON(json_str)

Улучшение Lovasoa очень хороший ответ.

Если вы используете Python 3.6+, вы можете использовать:
pip install marshmallow-enum а также
pip install marshmallow-dataclass

Это просто и безопасно.

Вы можете преобразовать свой класс в строку-json и наоборот:

От объекта к строке Json:

    from marshmallow_dataclass import dataclass
    user = User("Danilo","50","RedBull",15,OrderStatus.CREATED)
    user_json = User.Schema().dumps(user)
    user_json_str = user_json.data

От струны Джсон до объекта:

    json_str = '{"name":"Danilo", "orderId":"50", "productName":"RedBull", "quantity":15, "status":"Created"}'
    user, err = User.Schema().loads(json_str)
    print(user,flush=True)

Определения классов:

class OrderStatus(Enum):
    CREATED = 'Created'
    PENDING = 'Pending'
    CONFIRMED = 'Confirmed'
    FAILED = 'Failed'

@dataclass
class User:
    def __init__(self, name, orderId, productName, quantity, status):
        self.name = name
        self.orderId = orderId
        self.productName = productName
        self.quantity = quantity
        self.status = status

    name: str
    orderId: str
    productName: str
    quantity: int
    status: OrderStatus

Дацит также может быть решением для вас, он поддерживает следующие функции:

  • вложенные структуры
  • (базовая) проверка типов
  • необязательные поля (например, ввод. необязательно)
  • союзы
  • пересылка ссылок
  • коллекции
  • крючки нестандартного типа

https://pypi.org/project/dacite/

from dataclasses import dataclass
from dacite import from_dict


@dataclass
class User:
    name: str
    age: int
    is_active: bool


data = {
    'name': 'John',
    'age': 30,
    'is_active': True,
}

user = from_dict(data_class=User, data=data)

assert user == User(name='John', age=30, is_active=True)

JSON в объект Python

Следующий код рекурсивно создает динамические атрибуты с ключами объектов.

Объект JSON - fb_data.json:

      {
    "name": "John Smith",
    "hometown": {
        "name": "New York",
        "id": 123
    },
    "list": [
        "a",
        "b",
        "c",
        1,
        {
            "key": 1
        }
    ],
    "object": {
        "key": {
            "key": 1
        }
    }
}

По конвертации имеем 3 случая:

  • списки
  • dicts (новый объект)
  • bool, int, float и str
      import json


class AppConfiguration(object):
    def __init__(self, data=None):
        if data is None:
            with open("fb_data.json") as fh:
                data = json.loads(fh.read())
        else:
            data = dict(data)

        for key, val in data.items():
            setattr(self, key, self.compute_attr_value(val))

    def compute_attr_value(self, value):
        if isinstance(value, list):
            return [self.compute_attr_value(x) for x in value]
        elif isinstance(value, dict):
            return AppConfiguration(value)
        else:
            return value


if __name__ == "__main__":
    instance = AppConfiguration()

    print(instance.name)
    print(instance.hometown.name)
    print(instance.hometown.id)
    print(instance.list[4].key)
    print(instance.object.key.key)

Теперь пары ключ, значение - это атрибуты - объекты.

выход:

      John Smith
New York
123
1
1

Вставить JSON как код

Поддерживает TypeScript, Python, Go, Ruby, C#, Java, Swift, Rust, Kotlin, C++, Flow, Objective-C, JavaScript, Elm, и JSON Schema.

  • Интерактивное создание типов и (де-) сериализации кода из JSON, JSON Schema и TypeScript
  • Вставьте JSON / схему JSON / TypeScript как код

quicktype определяет типы из образца данных JSON, а затем выводит строго типизированные модели и сериализаторы для работы с этими данными на желаемом языке программирования.

выход:

      # Generated by https://quicktype.io
#
# To change quicktype's target language, run command:
#
#   "Set quicktype target language"

from typing import List, Union


class Hometown:
    name: str
    id: int

    def __init__(self, name: str, id: int) -> None:
        self.name = name
        self.id = id


class Key:
    key: int

    def __init__(self, key: int) -> None:
        self.key = key


class Object:
    key: Key

    def __init__(self, key: Key) -> None:
        self.key = key


class FbData:
    name: str
    hometown: Hometown
    list: List[Union[Key, int, str]]
    object: Object

    def __init__(self, name: str, hometown: Hometown, list: List[Union[Key, int, str]], object: Object) -> None:
        self.name = name
        self.hometown = hometown
        self.list = list
        self.object = object

Это расширение доступно бесплатно в Visual Studio Code Marketplace.

Думаю, самое легкое решение -

      import json
from typing import NamedTuple

_j = '{"name":"Иван","age":37,"mother":{"name":"Ольга","age":58},"children":["Маша","Игорь","Таня"],"married": true,' \
     '"dog":null} '


class PersonNameAge(NamedTuple):
    name: str
    age: int


class UserInfo(NamedTuple):
    name: str
    age: int
    mother: PersonNameAge
    children: list
    married: bool
    dog: str


j = json.loads(_j)
u = UserInfo(**j)

print(u.name, u.age, u.mother, u.children, u.married, u.dog)

>>> Ivan 37 {'name': 'Olga', 'age': 58} ['Mary', 'Igor', 'Jane'] True None

Если вы ищете безопасную десериализацию JSON или любого сложного словаря в класс Python, я настоятельно рекомендую pydantic для Python 3.7+. Он не только имеет краткий API (не требует написания «вспомогательного» шаблона), может интегрироваться с классами данных Python, но также имеет статическую проверку типа и во время выполнения для сложных и вложенных структур данных.

Пример использования:

      from pydantic import BaseModel
from datetime import datetime

class Item(BaseModel):
    field1: str | int           # union
    field2: int | None = None   # optional
    field3: str = 'default'     # default values

class User(BaseModel):
    name: str | None = None
    username: str
    created: datetime           # default type converters
    items: list[Item] = []      # nested complex types

data = {
    'name': 'Jane Doe',
    'username': 'user1',
    'created': '2020-12-31T23:59:00+10:00',
    'items': [
        {'field1': 1, 'field2': 2},
        {'field1': 'b'},
        {'field1': 'c', 'field3': 'override'}
    ]
}

user: User = User(**data)

Для получения более подробной информации и функций ознакомьтесь с рациональным разделом pydantic в их документации.

Я написал небольшую (де) сериализационную среду под названием any2any, которая помогает выполнять сложные преобразования между двумя типами Python.

В вашем случае, я думаю, вы хотите преобразовать из словаря (полученного с json.loads) к сложному объекту response.education ; response.nameс вложенной структурой response.education.idи т. д. Итак, именно для этого и создан этот фреймворк. Документация пока не отличная, но с помощью any2any.simple.MappingToObjectВы должны быть в состоянии сделать это очень легко. Пожалуйста, спросите, нужна ли вам помощь.

dataclass-wizard — это современный вариант, который может сработать и для вас. Он поддерживает автоматические преобразования регистра ключей , такие как camelCase или TitleCase, оба из которых довольно распространены в ответах API.

Ключевое преобразование по умолчанию при сбросе экземпляра в /JSON — это camelCase, но его можно легко переопределить с помощью конфигурации Meta, предоставленной в основном классе данных.

https://pypi.org/project/dataclass-wizard/

      from dataclasses import dataclass

from dataclass_wizard import fromdict, asdict


@dataclass
class User:
    name: str
    age: int
    is_active: bool


data = {
    'name': 'John',
    'age': 30,
    'isActive': True,
}

user = fromdict(User, data)
assert user == User(name='John', age=30, is_active=True)

json_dict = asdict(user)
assert json_dict == {'name': 'John', 'age': 30, 'isActive': True}

Пример настройки метаконфига, который конвертирует поля в lisp-case при сериализации в dict/JSON:

      DumpMeta(key_transform='LISP').bind_to(User)

Немного расширив ответ DS, если вам нужно, чтобы объект был изменяемым (это не namedtuple), вы можете использовать библиотеку recordclass вместо namedtuple:

import json
from recordclass import recordclass

data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'

# Parse into a mutable object
x = json.loads(data, object_hook=lambda d: recordclass('X', d.keys())(*d.values()))

Модифицированный объект может быть легко преобразован обратно в json с помощью simplejson:

x.name = "John Doe"
new_json = simplejson.dumps(x)

В поисках решения я наткнулся на этот пост в блоге: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

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

class JsonConvert(object):
    class_mappings = {}

    @classmethod
    def class_mapper(cls, d):
        for keys, cls in clsself.mappings.items():
            if keys.issuperset(d.keys()):   # are all required arguments present?
                return cls(**d)
        else:
            # Raise exception instead of silently returning None
            raise ValueError('Unable to find a matching class for object: {!s}'.format(d))

    @classmethod
    def complex_handler(cls, Obj):
        if hasattr(Obj, '__dict__'):
            return Obj.__dict__
        else:
            raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj)))

    @classmethod
    def register(cls, claz):
        clsself.mappings[frozenset(tuple([attr for attr,val in cls().__dict__.items()]))] = cls
        return cls

    @classmethod
    def to_json(cls, obj):
        return json.dumps(obj.__dict__, default=cls.complex_handler, indent=4)

    @classmethod
    def from_json(cls, json_str):
        return json.loads(json_str, object_hook=cls.class_mapper)

Использование:

@JsonConvert.register
class Employee(object):
    def __init__(self, Name:int=None, Age:int=None):
        self.Name = Name
        self.Age = Age
        return

@JsonConvert.register
class Company(object):
    def __init__(self, Name:str="", Employees:[Employee]=None):
        self.Name = Name
        self.Employees = [] if Employees is None else Employees
        return

company = Company("Contonso")
company.Employees.append(Employee("Werner", 38))
company.Employees.append(Employee("Mary"))

as_json = JsonConvert.to_json(company)
from_json = JsonConvert.from_json(as_json)
as_json_from_json = JsonConvert.to_json(from_json)

assert(as_json_from_json == as_json)

print(as_json_from_json)

Уже есть несколько жизнеспособных ответов, но есть несколько небольших библиотек, созданных отдельными людьми, которые могут помочь большинству пользователей.

Примером может быть json2object. Учитывая определенный класс, он десериализует данные json в вашу настраиваемую модель, включая настраиваемые атрибуты и дочерние объекты.

Его использование очень простое. Пример из вики библиотеки:

         from json2object import jsontoobject as jo

class Student:
    def __init__(self):
        self.firstName = None
        self.lastName = None
        self.courses = [Course('')]

class Course:
    def __init__(self, name):
        self.name = name

data = '''{
"firstName": "James",
"lastName": "Bond",
"courses": [{
    "name": "Fighting"},
    {
    "name": "Shooting"}
    ]
}
'''

model = Student()
result = jo.deserialize(data, model)
print(result.courses[0].name)

Приведенные здесь ответы не возвращают правильный тип объекта, поэтому я создал эти методы ниже. Они также терпят неудачу, если вы попытаетесь добавить в класс дополнительные поля, которых нет в данном JSON:

def dict_to_class(class_name: Any, dictionary: dict) -> Any:
    instance = class_name()
    for key in dictionary.keys():
        setattr(instance, key, dictionary[key])
    return instance


def json_to_class(class_name: Any, json_string: str) -> Any:
    dict_object = json.loads(json_string)
    return dict_to_class(class_name, dict_object)
      class SimpleClass:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            if type(v) is dict:
                setattr(self, k, SimpleClass(**v))
            else:
                setattr(self, k, v)


json_dict = {'name': 'jane doe', 'username': 'jane', 'test': {'foo': 1}}

class_instance = SimpleClass(**json_dict)

print(class_instance.name, class_instance.test.foo)
print(vars(class_instance))

Немного изменив ответ @DS, чтобы загрузить из файла:

def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def load_data(file_name):
  with open(file_name, 'r') as file_data:
    return file_data.read().replace('\n', '')
def json2obj(file_name): return json.loads(load_data(file_name), object_hook=_json_object_hook)

Одна вещь: это не может загружать предметы с номерами впереди. Как это:

{
  "1_first_item": {
    "A": "1",
    "B": "2"
  }
}

Потому что "1_first_item" не является допустимым именем поля Python.

Я искал решение, которое работает с recordclass.RecordClass, поддерживает вложенные объекты и работает как для сериализации json, так и для десериализации json.

Расширяя ответ DS и расширяя решение от BeneStr, я придумал следующее, которое, похоже, работает:

Код:

import json
import recordclass

class NestedRec(recordclass.RecordClass):
    a : int = 0
    b : int = 0

class ExampleRec(recordclass.RecordClass):
    x : int       = None
    y : int       = None
    nested : NestedRec = NestedRec()

class JsonSerializer:
    @staticmethod
    def dumps(obj, ensure_ascii=True, indent=None, sort_keys=False):
        return json.dumps(obj, default=JsonSerializer.__obj_to_dict, ensure_ascii=ensure_ascii, indent=indent, sort_keys=sort_keys)

    @staticmethod
    def loads(s, klass):
        return JsonSerializer.__dict_to_obj(klass, json.loads(s))

    @staticmethod
    def __obj_to_dict(obj):
        if hasattr(obj, "_asdict"):
            return obj._asdict()
        else:
            return json.JSONEncoder().default(obj)

    @staticmethod
    def __dict_to_obj(klass, s_dict):
        kwargs = {
            key : JsonSerializer.__dict_to_obj(cls, s_dict[key]) if hasattr(cls,'_asdict') else s_dict[key] \
                for key,cls in klass.__annotations__.items() \
                    if s_dict is not None and key in s_dict
        }
        return klass(**kwargs)

Применение:

example_0 = ExampleRec(x = 10, y = 20, nested = NestedRec( a = 30, b = 40 ) )

#Serialize to JSON

json_str = JsonSerializer.dumps(example_0)
print(json_str)
#{
#  "x": 10,
#  "y": 20,
#  "nested": {
#    "a": 30,
#    "b": 40
#  }
#}

# Deserialize from JSON
example_1 = JsonSerializer.loads(json_str, ExampleRec)
example_1.x += 1
example_1.y += 1
example_1.nested.a += 1
example_1.nested.b += 1

json_str = JsonSerializer.dumps(example_1)
print(json_str)
#{
#  "x": 11,
#  "y": 21,
#  "nested": {
#    "a": 31,
#    "b": 41
#  }
#}

Вы можете использовать

x = Map(json.loads(response))
x.__class__ = MyClass

где

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v
                    if isinstance(v, dict):
                        self[k] = Map(v)

        if kwargs:
            # for python 3 use kwargs.items()
            for k, v in kwargs.iteritems():
                self[k] = v
                if isinstance(v, dict):
                    self[k] = Map(v)

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Для универсального решения, ориентированного на будущее.

Поэтому я искал способ демаршалировать любой произвольный тип (например, dict класса данных или dict dict массива класса данных) без тонны пользовательского кода десериализации.

Это мой подход:

      import json
from dataclasses import dataclass, make_dataclass

from dataclasses_json import DataClassJsonMixin, dataclass_json


@dataclass_json
@dataclass
class Person:
    name: str


def unmarshal_json(data, t):
    Unmarhsal = make_dataclass('Unmarhsal', [('res', t)],
                               bases=(DataClassJsonMixin,))
    d = json.loads(data)
    out = Unmarhsal.from_dict({"res": d})
    return out.res


unmarshalled = unmarshal_json('{"1": {"name": "john"} }', dict[str, Person])
print(unmarshalled)

Отпечатки:{'1': Person(name='john')}

      def load_model_from_dict(self, data: dict):
    for key, value in data.items():
        self.__dict__[key] = value
    return self

Это помогает вернуть вашу собственную модель с непредвиденными переменными из dict.

Если вы используете Python 3.6 или новее, вы можете взглянуть на squema - легкий модуль для статически типизированных структур данных. Это упрощает чтение вашего кода и в то же время обеспечивает простую проверку, преобразование и сериализацию данных без дополнительной работы. Вы можете думать об этом как о более сложной и самоуверенной альтернативе именованным кортежам и классам данных. Вот как вы могли бы это использовать:

from uuid import UUID
from squema import Squema


class FbApiUser(Squema):
    id: UUID
    age: int
    name: str

    def save(self):
        pass


user = FbApiUser(**json.loads(response))
user.save()

Похоже, это вопрос AB (вопрос A, где на самом деле проблема B).

Корень проблемы: как эффективно ссылаться / изменять глубоко вложенные структуры JSON без необходимости делать ob['foo']['bar'][42]['quux'], что создает проблему ввода, код -выпуск, проблема с читабельностью и проблема с отловом ошибок?

Использовать glom

https://glom.readthedocs.io/en/latest/tutorial.html

      from glom import glom

# Basic deep get

data = {'a': {'b': {'c': 'd'}}}

print(glom(data, 'a.b.c'))

Он также будет обрабатывать элементы списка: glom(data, 'a.b.c.42.d')

Я сравнил это с наивной реализацией:

      def extract(J, levels):
    # Twice as fast as using glom
    for level in levels.split('.'):
        J = J[int(level) if level.isnumeric() else level]
    return J

... и он возвращает 0,14 мс для сложного объекта JSON по сравнению с 0,06 мс для наивного impl.

Он также может обрабатывать сложные запросы, например, извлекать все foo.bar.records куда .name == 'Joe Bloggs'

РЕДАКТИРОВАТЬ:

Другой эффективный подход - рекурсивно использовать класс, который переопределяет __getitem__ а также __getattr__:

      class Ob:
    def __init__(self, J):
        self.J = J

    def __getitem__(self, index):
        return Ob(self.J[index])

    def __getattr__(self, attr):
        value = self.J.get(attr, None)
        return Ob(value) if type(value) in (list, dict) else value

Теперь вы можете:

      ob = Ob(J)

# if you're fetching a final raw value (not list/dict
ob.foo.bar[42].quux.leaf

# for intermediate values
ob.foo.bar[42].quux.J

Это тоже на удивление хорошо. Сравнимо с моим предыдущим наивным имп. Если кто-то может найти способ навести порядок в доступе для запросов, не являющихся листовыми, оставьте комментарий!

Python3.x

Наилучшим подходом, которого я мог достичь с помощью моих знаний, было это.
Обратите внимание, что этот код также обрабатывает set().
Этот подход является общим, просто нуждающимся в расширении класса (во втором примере).
Обратите внимание, что я просто делаю это с файлами, но легко изменить поведение на свой вкус.

Однако это кодек.

Приложив немного больше работы, вы можете построить свой класс другими способами. Я предполагаю, что конструктор по умолчанию его создает, а затем обновляю класс dict.

import json
import collections


class JsonClassSerializable(json.JSONEncoder):

    REGISTERED_CLASS = {}

    def register(ctype):
        JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in self.REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = self.REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


JsonClassSerializable.register(C)


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


JsonClassSerializable.register(B)


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()

JsonClassSerializable.register(A)

A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)

редактировать

Проведя дополнительные исследования, я нашел способ обобщения без необходимости вызова метода регистра SUPERCLASS с использованием метакласса.

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in REGISTERED_CLASS:
            REGISTERED_CLASS[cls.__name__] = cls
        return super(MetaSerializable, cls).__call__(*args, **kwargs)


class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()


A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s

Вот мой путь.

Функции

  • подсказки типа поддержки
  • поднять ошибку, если ключ отсутствует.
  • пропустить дополнительное значение в данных
      import typing

class User:
    name: str
    age: int

    def __init__(self, data: dict):
        for k, _ in typing.get_type_hints(self).items():
            setattr(self, k, data[k])

data = {
    "name": "Susan",
    "age": 18
}

user = User(data)
print(user.name, user.age)

# Output: Susan 18
Другие вопросы по тегам