Flask-RESTful - 不要 return object 属性 而不是 returning null

Flask-RESTful - don't return object property instead of returning null

假设我有一个客户 table,有 idnameemail 字段。 电子邮件 字段是可选

代码如下所示:

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 returns JSON 像这样:

{
   "id": "1",
   "name": "John Doe",
   "email": null
}

但我希望它 return 这个对象:

{
   "id": "1",
   "name": "John Doe"
}

这基本上意味着我希望 return 完全没有这样的 属性,而不是具有空值的 属性。有办法实现吗?

我会使用 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}

有两种方法可以完成,预编组和post-编组修改。预编组会删除 client_fields dict 中字段名称的任何默认值,但 post-编组会保留它们。

在预编组方法中,如果客户的电子邮件是 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"}]

在 post-编组中,如果 marshal_with 返回的 OrderedDictNone.
,则必须删除其电子邮件字段 默认情况下,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 请求挂钩采用 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"
    }
]

您应该使用@marshal 装饰器的skip_none 属性。它比其他答案中建议的方法方便得多。

@marshal(some_model, skip_none=True)
def get():
   ...

可在此处找到文档:https://flask-restplus.readthedocs.io/en/stable/marshalling.html

这也可以使用 Flask Restx :)