我想将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对象,是不是会更好?


当前回答

如果你使用的是Python 3.5+,你可以使用json来序列化和反序列化到普通的旧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需要自定义类型的类型提示。

其他回答

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

这是我的方法:

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

这似乎是一个XY问题(问A实际问题在哪里B)。

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

使用抢

from glom import glom

# Basic deep get

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

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

它还将处理列表项:

我已经对一个简单的实现进行了基准测试:

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。

它还可以处理复杂的查询,例如取出所有foo.bar.记录,其中.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

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

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

这是一个健壮的类,可以轻松地在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)

如果你使用的是Python 3.5+,你可以使用json来序列化和反序列化到普通的旧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需要自定义类型的类型提示。

Dacite也可能是您的解决方案,它支持以下功能:

嵌套结构 (基本)类型检查 可选字段(即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)