如何将JSON数据转换为Python对象?

我想将JSON数据转换为Python对象。

我从Facebook API收到JSON数据对象,我想将其存储在数据库中。

我当前在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对象,是不是会更好?
694412 次浏览

使用几乎总是安装的json模块 (Python 2.6新增功能)或simplejson模块。

对于复杂对象,可以使用JSON泡菜

用于将任意对象图序列化为JSON的Python库。 它几乎可以接受任何Python对象并将对象转换为JSON。 此外,它还可以将对象重新构造回Python

查看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']

就像一本普通的字典。

我写了一个名为any2any的小(反)序列化框架,它可以帮助在两种Python类型之间进行复杂的转换。

在你的情况下,我猜你想从一个字典(通过json.loads获得)转换到一个复杂对象response.education ; response.name,具有嵌套结构response.education.id,等等… 这就是这个框架的用途。文档还不是很好,但是通过使用any2any.simple.MappingToObject,你应该可以很容易地做到这一点。

.如果你需要帮助,请询问

更新

在Python3中,你可以在一行中使用SimpleNamespaceobject_hook完成:

import json
from types import SimpleNamespace


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: SimpleNamespace(**d))
print(x.name, x.hometown.name, x.hometown.id)

旧答案(Python2)

在Python2中,你可以在一行中使用namedtupleobject_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)

如果你想让它处理不是很好的属性名的键,请检查namedtuplerename参数

你可以试试这个:

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


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

只需创建一个新对象,并将参数作为映射传递。


你也可以有一个带有对象的JSON:

import json
class Address(object):
def __init__(self, street, number):
self.street = street
self.number = number


def __str__(self):
return "{0} {1}".format(self.street, self.number)


class User(object):
def __init__(self, name, address):
self.name = name
self.address = Address(**address)


def __str__(self):
return "{0} ,{1}".format(self.name, self.address)


if __name__ == '__main__':
js = '''{"name":"Cristian", "address":{"street":"Sesame","number":122}}'''
j = json.loads(js)
print(j)
u = User(**j)
print(u)

这里有一个快速而肮脏的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()

这不是代码高尔夫,但这里是我最短的技巧,使用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)

修改@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字段名。

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)

编辑

通过更多的研究,我发现了一种不需要调用超类寄存器方法的泛化方法,使用元类

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

在寻找解决方案时,我偶然发现了这篇博客文章:https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

它使用与前面的回答相同的技术,但使用了decorator。 我发现另一件有用的事情是,它在反序列化结束时返回一个类型化对象

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)

如果你使用的是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库需要自定义类型的类型提示。

扩展一下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()))

修改后的对象可以使用simplejson很容易地转换回json:

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

既然没有人给出了和我一样的答案,我就把它贴在这里。

它是一个健壮的类,可以轻松地在我从我对另一个问题的回答复制的JSON strdict之间来回转换:

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)

如果你正在使用python 3.6+,你可以使用marshmallow-dataclass。与上面列出的所有解决方案相反,它既简单,又类型安全:

from marshmallow_dataclass import dataclass


@dataclass
class User:
name: str


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

改进lovasoa非常好的答案。

如果你正在使用python 3.6+,你可以使用:
pip install marshmallow-enum和< br > pip install marshmallow-dataclass < / p >

它简单且类型安全。

你可以在string-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

从String Json到Object:

    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

如果你使用的是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()

你可以使用

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]

对于通用的、经得起未来考验的解决方案。

我正在寻找一个解决方案,工作与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
#  }
#}

这里给出的答案没有返回正确的对象类型,因此我在下面创建了这些方法。如果你试图向给定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)

英安岩也可能是你的解决方案,它支持以下功能:

  • 嵌套结构
  • (基本)类型检查
  • 可选字段(即typing.Optional)
  • 工会
  • 向前引用
  • 集合
  • 自定义类型钩子

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)

已经有多种可行的答案,但有一些由个人制作的小型库可以满足大多数用户的需求。

一个例子是json2object。给定一个已定义的类,它将json数据反序列化到您的自定义模型,包括自定义属性和子对象。

它的使用非常简单。一个来自图书馆wiki的例子:

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)

我认为最简单的解决方法是

import orjson  # faster then 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 = orjson.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对象

下面的代码递归地使用对象键创建动态属性。

JSON对象- fb_data.json:

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

在转换中我们有三种情况:

  • 列表
  • 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作为代码粘贴

支持TypeScriptPython GoRuby C#, Java, Swift, Rust, Kotlin, C++, Python0, Python1, Python2 Python3, Python4。

  • 从JSON、JSON Schema和TypeScript中交互式地生成类型和(反)序列化代码
  • 将JSON/JSON Schema/TypeScript作为代码粘贴

enter image description here

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代码市场中免费使用。

这不是一个很难的事情,我看到上面的答案,他们中的大多数都有一个性能问题在“列表”。

这段代码比上面的代码快得多

import json


class jsonify:
def __init__(self, data):
self.jsonify = data


def __getattr__(self, attr):
value = self.jsonify.get(attr)
if isinstance(value, (list, dict)):
return jsonify(value)
return value


def __getitem__(self, index):
value = self.jsonify[index]
if isinstance(value, (list, dict)):
return jsonify(value)
return value


def __setitem__(self, index, value):
self.jsonify[index] = value


def __delattr__(self, index):
self.jsonify.pop(index)


def __delitem__(self, index):
self.jsonify.pop(index)


def __repr__(self):
return json.dumps(self.jsonify, indent=2, default=lambda x: str(x))

例子

response = jsonify(
{
'test': {
'test1': [{'ok': 1}]
}
}
)
response.test -> jsonify({'test1': [{'ok': 1}]})
response.test.test1 -> jsonify([{'ok': 1}])
response.test.test1[0] -> jsonify({'ok': 1})
response.test.test1[0].ok -> int(1)
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))

这似乎是一个XY问题(询问A的实际问题是B)。

问题的根源是:如何有效地引用/修改深嵌套的JSON结构,而不必执行obj['foo']['bar'][42]['quux'],这会带来键入挑战、代码膨胀问题、可读性问题和错误捕获问题?

使用glom

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

... 并且在复杂的JSON对象上返回0.14ms,而朴素的impl则返回0.06ms。

它还可以处理复杂的查询,例如从.name == 'Joe Bloggs' . xml中取出所有foo.bar.records

编辑:

另一种性能方法是递归地使用覆盖__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

这一基准测试也出奇地好。与我之前的天真冲动相当。如果有人能找到一种方法来整理非叶查询的访问,请留下评论!

dataclass-wizard是一个现代的选项,可以类似地为你工作。它支持自动关键的套管转换,例如camelCaseTitleCase,这两者在API响应中非常常见。

当将实例转储为dict/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}

设置元配置的示例,当序列化为dict/JSON时,将字段转换为lisp-case:

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

这是我的办法。

特性

  • 支持类型提示
  • 如果缺少键则引发错误。
  • 跳过数据中的额外值
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
def load_model_from_dict(self, data: dict):
for key, value in data.items():
self.__dict__[key] = value
return self

它帮助返回你自己的模型,从字典中不可预见的变量。

因此,我正在寻找一种不需要大量自定义反序列化代码就能解组任意类型(想想数据类的字典,或者数据类数组的字典的字典)的方法。

这是我的方法:

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')}

如果你正在寻找JSON或任何复杂字典的类型安全反序列化到python类中,我强烈建议python 3.7+使用pydantic。它不仅有一个简洁的API(不需要编写“helper”样板),可以与Python dataclasses集成,而且具有复杂和嵌套数据结构的静态和运行时类型验证。

使用示例:

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文档中的理性的部分。