Flask-RESTful - 不返回对象属性而不是返回 null
Posted
技术标签:
【中文标题】Flask-RESTful - 不返回对象属性而不是返回 null【英文标题】:Flask-RESTful - don't return object property instead of returning null 【发布时间】:2015-05-23 17:42:25 【问题描述】:假设我有一个包含 id、name 和 email 字段的客户表。 email 字段是可选。
代码如下所示:
client_fields =
'id' : fields.String,
'name' : fields.String,
'email' : fields.String
用于显示:
class ClientList(Resource):
@marshal_with(client_fields)
def get(self):
return model.Client.query.all()
如果未提供电子邮件,API 会返回如下 JSON:
"id": "1",
"name": "John Doe",
"email": null
但我希望它返回这个对象:
"id": "1",
"name": "John Doe"
这基本上意味着我希望它根本不返回此类属性,而不是具有空值的属性。有没有办法做到这一点?
【问题讨论】:
这通常不是一个好的设计决策。任何正在寻找电子邮件字段的 API 使用者现在都需要特殊处理来处理该字段是否存在。对于您的消费者来说,拥有一个空字段而不是一个可能存在也可能不存在的字段要安全得多。 我不太同意这一点。对象可以来自多个数据源并且有大量不完整的数据。根据具体情况,有时这是一个完全合法的决定。 【参考方案1】:我会使用marshal
函数而不是marshal_with
装饰器:
class ClientList(Resource):
def get(self):
clients = []
for client in model.Client.query.all():
if client.email:
clients.append(marshal(client_fields))
else:
clients.append(marshal(client_fields_no_email))
return clients
甚至更好
class ClientList(Resource):
def get(self):
return [client_marshal(client) for client in model.Client.query.all()]
与
def client_marshal(client):
if client.email:
return 'id' : fields.String,
'name' : fields.String,
'email' : fields.String
else:
return 'id' : fields.String,
'name' : fields.String
【讨论】:
这适用于可能的修改数量相对较少的情况,但是如果您想根据 oauth 范围或基于用户隐私设置等显示不同的字段集怎么办。 @bigblind 是的,确实我来这里是为了一个有很多解决方案的解决方案。【参考方案2】:有两种方法可以完成,预编组和后编组修改。预编组删除了为 client_fields dict
中的字段名称提供的任何默认值,但后编组保留了它们。
在预编组方法中,如果客户的电子邮件是None
,则必须将修改后的字段dict
传递给marshal
函数。
例如;
import json
from flask_restful import fields, marshal, marshal_with
class Client(object):
def __init__(self, id, name, email=None):
self.id = id
self.name = name
self.email = email
client_fields =
'id': fields.String,
'name': fields.String,
'email': fields.String
def get():
clients =[Client(1, 'Tom'), Client(2, 'John', 'john@example.com')]
return [marshal(client, client_fields if client.email else k: v for k, v in client_fields.items() if k != 'email') for client in clients]
print(json.dumps(get()))
输出;
["id": "1", "name": "Tom", "email": "john@example.com", "id": "2", "name": "John"]
在编组后,您必须删除marshal_with
返回的OrderedDict
的电子邮件字段,如果它是None
。
默认情况下,de_none
函数会删除所有 None
字段,或者如果不需要,您必须显式传递字段名称,并且如果 marshal_with
采用相同的参数,您也必须传递 envelope
参数。
from functools import wraps
import json
from flask_restful import fields, marshal, marshal_with
client_fields =
'id': fields.String,
'name': fields.String,
#'email': fields.String(default='user@example.com')
'email': fields.String
class Client(object):
def __init__(self, id, name, email=None):
self.id = id
self.name = name
self.email = email
def de_none(envelope=None, *fields):
def decorator(func):
def dict_remove(d):
if fields:
for field in fields:
if d[field] is None:
d.pop(field)
else:
for k, v in d.items():
if v is None:
d.pop(k)
@wraps(func)
def decorated(*args, **kwargs):
data = result = func(*args, **kwargs)
if isinstance(result, tuple):
data = result[0]
if envelope:
data = data[envelope]
if isinstance(data, (list, tuple)):
for d in data:
dict_remove(d)
else:
dict_remove(data)
return result
return decorated
return decorator
@de_none()
@marshal_with(client_fields)
def get():
#return [Client(1, 'Tom'), Client(2, 'john', 'john@example.com')], 200, 'Etag': 'blah'
#return [Client(1, 'Tom'), Client(2, 'john', 'john@example.com')]
#return Client(1, 'Tom'), 200, 'Etag': 'foo'
return Client(1, 'Tom')
print(json.dumps(get()))
@de_none()
@marshal_with(client_fields)
def get():
return Client(2, 'John', 'john@example.com'), 201, 'Etag': 'ok'
print(json.dumps(get()))
输出;
"id": "1", "name": "Tom"
"email": "john@example.com", "id": "2", "name": "John"
更新 请求挂钩app.after_request
装饰器可用于修改响应对象。保留给字段的任何默认值。remove_none_fields
请求钩子接受可以是 None
的字段参数来删除所有具有 None
值的字段或要选择性删除的字段名称列表。
import json
from flask import Flask, Response
from flask_restful import fields, marshal_with, Api, Resource
app = Flask(__name__)
api = Api(app)
class Client(object):
def __init__(self, id, name, email=None):
self.id = id
self.name = name
self.email = email
client_fields =
'id': fields.String,
'name': fields.String,
'email': fields.String,
'age': fields.String
class ClientList(Resource):
@marshal_with(client_fields)
def get(self):
clients =[Client(1, 'Tom'), Client(2, 'John', 'john@example.com')]
return clients, 200
@app.after_request
def remove_none_fields(resp, fields=('email',)):
"""
removes all None fields
"""
if not 'application/json' in resp.content_type:
return resp
def dict_remove(d, fields):
if fields:
for field in fields:
if d[field] is None:
d.pop(field)
else:
for k, v in tuple(d.items()):
if v is None:
d.pop(k)
data = json.loads(resp.get_data())
if isinstance(data, list):
for obj in data:
dict_remove(obj, fields)
else:
dict_remove(data, fields)
resp.set_data(json.dumps(data, indent=1))
resp.content_length = resp.calculate_content_length()
return resp
api.add_resource(ClientList, '/')
if __name__ == '__main__':
app.run(debug=True)
输出;
[
"age": null,
"name": "Tom",
"id": "1"
,
"age": null,
"email": "john@example.com",
"name": "John",
"id": "2"
]
更新 修补 flask_restful.marshal
我在 marshal
函数内的 genexp 中过滤掉 None
值,并将 flask_restful.marshal
替换为此处定义的 marshal
。
from collections import OrderedDict
from flask import Flask
import flask_restful
from flask_restful import fields, marshal_with, Api, Resource
app = Flask(__name__)
api = Api(app)
class Client(object):
def __init__(self, id, name, email=None):
self.id = id
self.name = name
self.email = email
client_fields =
'id': fields.String,
'name': fields.String,
'email': fields.String,
def marshal(data, fields, envelope=None):
def make(cls):
if isinstance(cls, type):
return cls()
return cls
if isinstance(data, (list, tuple)):
return (OrderedDict([(envelope, [marshal(d, fields) for d in data])])
if envelope else [marshal(d, fields) for d in data])
items = ((k, marshal(data, v) if isinstance(v, dict)
else make(v).output(k, data))
for k, v in fields.items())
#filtering None
items = ((k,v) for k, v in items if v is not None)
return OrderedDict([(envelope, OrderedDict(items))]) if envelope else OrderedDict(items)
flask_restful.marshal = marshal
class ClientList(Resource):
@marshal_with(client_fields)
def get(self):
clients =[Client(1, 'Tom'), Client(2, 'John', 'john@example.com')]
return clients, 200
api.add_resource(ClientList, '/')
if __name__ == '__main__':
app.run(debug=True)
输出;
[
"id": "1",
"name": "Tom"
,
"email": "john@example.com",
"id": "2",
"name": "John"
]
【讨论】:
嘿否决选民,请留下评论,以便人们从错误中吸取教训。【参考方案3】:您应该使用 @marshal 装饰器的 skip_none 属性。它比其他答案中建议的方法方便得多。
@marshal(some_model, skip_none=True)
def get():
...
文档可以在这里找到:https://flask-restplus.readthedocs.io/en/stable/marshalling.html
这也可以使用 Flask Restx :)
【讨论】:
以上是关于Flask-RESTful - 不返回对象属性而不是返回 null的主要内容,如果未能解决你的问题,请参考以下文章
使用 flask-restful 时返回 text/html 内容类型
从初始化程序返回而不初始化所有存储的属性错误 Firebase