我想将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对象,是不是会更好?
如果你正在寻找将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部分。
这里有一个快速而肮脏的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()
改进lovasoa非常好的答案。
如果你正在使用python 3.6+,你可以使用:
PIP安装棉花糖-enum和
PIP安装棉花糖数据类
它简单且类型安全。
你可以在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
这不是一个很难的事情,我看到上面的答案,他们中的大多数在“列表”中有一个性能问题
这段代码比上面的代码快得多
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))
exmaple
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)
这似乎是一个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
这一基准测试也出奇地好。与我之前的天真冲动相当。如果有人能找到一种方法来整理非叶查询的访问,请留下评论!
扩展一下DS的答案,如果你需要对象是可变的(而namedtuple不是),你可以使用记录类库而不是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)