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

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

我的当前视图在Django (Python)(请求。POST包含JSON):

response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()

这很好,但是如何处理复杂的JSON数据对象呢? 如果我能以某种方式将这个JSON对象转换为易于使用的Python对象,是不是会更好?


当前回答

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

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

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

从json2object导入jsontoobject作为Jo 类学生: def __init__(自我): 自我。firstName =无 自我。lastName = None 自我。courses =[课程(")] 类课程: 定义__init__(self, name): Self.name = name 数据= " '{ “firstName”:“詹姆斯”, “姓”:“债券”, “课程”:[{ “名称”:“战斗”}, { “名称”:“射击”} ] } “‘ model = Student() Result = jo.deserialize(数据,模型) print (result.courses [0] . name)

其他回答

这是我的办法。

特性

支持类型提示 如果缺少键则引发错误。 跳过数据中的额外值

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

这不是代码高尔夫,但这里是我使用类型的最短技巧。SimpleNamespace作为JSON对象的容器。

与namedtuple解决方案相比,它是:

可能更快/更小,因为它没有为每个对象创建一个类 更短的 没有重命名选项,对于不是有效标识符的键可能有相同的限制(在幕后使用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或任何复杂字典的类型安全反序列化到python类中,我强烈推荐python 3.7+的pydantic。它不仅有一个简洁的API(不需要编写“helper”样板),可以与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的rational部分。

数据类向导是一种现代的选项,可以类似地为您工作。它支持自动键大小写转换,如camelCase或TitleCase,这两者在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)

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