Flask-RESTful - 不返回object属性而不是返回null

时间:2015-03-20 10:30:10

标签: python json python-2.7 null flask-restful

假设我的客户表中包含 id 名称电子邮件字段。 电子邮件字段为可选

代码如下所示:

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"
}

这基本上意味着我想要返回没有这样的属性而不是具有null值的属性。有没有办法实现这个目标?

2 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:9)

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

在预编组方法中,如果客户的电子邮件为dict,则必须将修改后的字段marshal传递给None。 例如;

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"}]

在编组后,您必须删除OrderedDict返回的marshal_with的电子邮件字段,如果它是None
默认情况下,de_none函数会删除None的所有字段,或者如果不需要则必须显式传递字段名称,如果envelope,则必须传递marshal_with参数采取相同的做法。

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
我在None函数内的genexp中过滤掉marshal值,并将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"
    }
]