Flask-RESTful - 不返回对象属性而不是返回 null

Posted

技术标签:

【中文标题】Flask-RESTful - 不返回对象属性而不是返回 null【英文标题】:Flask-RESTful - don't return object property instead of returning null 【发布时间】:2015-05-23 17:42:25 【问题描述】:

假设我有一个包含 idnameemail 字段的客户表。 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设计风格

使用 flask-restful 时返回 text/html 内容类型

从初始化程序返回而不初始化所有存储的属性错误 Firebase

从初始化程序返回而不初始化所有存储的属性swift ios

flask,flask-restful接口返回值无法显示中文

Flask 学习-36.Flask-RESTful 序列化输出对象