Namedtuple 在 Flask 中无法正确呈现

Namedtuple are not rendering properly in Flask

我正在使用 Flask/Python 应用程序并在 jinja 模板中呈现 namedtuple 类型数据时遇到问题。

我正在使用版本

下面是我的工作示例代码:

from collections import namedtuple
from flask import Flask, render_template, render_template_string

# ....
app = Flask(__name__)

Category = namedtuple(
'Category',
['name', 'priority', 'menus', 'has_next_level']
)

MenuItem = namedtuple(
    'MenuItem',
    ['title', 'url', 'target', 'id', 'parent_id', 'object_type']
)

# --- Sample input value render_template()
menu_content = {
    50: {
        'AGENTS': Category(
            name='AGENTS', 
            priority=1, 
            menus=[
                MenuItem(
                    title='Agent2', 
                    url='/monitoring/dashboard/6/agent/2', 
                    target=None, 
                    id=2, 
                    parent_id=None, 
                    object_type='agent'
                    ), 
                MenuItem(
                    title='Postgres Enterprise Manager Host', 
                    url='/monitoring/dashboard/6/agent/1', 
                    target=None, 
                    id=1, 
                    parent_id=None, 
                    object_type='agent'
                )], 
            has_next_level=True
        )
    }
}

output = None
with app.app_context():
    output = render_template_string(
        '{{ menu_content|tojson|safe }}',
        menu_content=menu_content
    )

print(output)

输出,我得到:

{"50": {"AGENTS": ["AGENTS", 1, [["Agent2", "/monitoring/dashboard/6/agent/2", null, 2, null, "agent"], ["Postgres Enterprise Manager Host", "/monitoring/dashboard/6/agent/1", null, 1, null, "agent"]], true]}

预期输出:

{"50": {"AGENTS": {"has_next_level": true, "menus": [{"id": 2, "object_type": "agent", "parent_id": null, "target": null, "title": "Agent2", "url": "/monitoring/dashboard/6/agent/2"}, {"id": 1, "object_type": "agent", "parent_id": null, "target": null, "title": "Postgres Enterprise Manager Host", "url": "/monitoring/dashboard/6/agent/1"}]}

我在这里遗漏了什么吗?

感谢您为您的问题添加详细信息,以便我可以重现您的问题。

  1. 解决您的问题
  2. 说明

解决方案:

from collections import namedtuple
from flask import Flask, render_template, render_template_string
import json
from collections import OrderedDict

# Function that will be used to convert the namedtuple to a dict
def namedtuple_asdict(obj):
    if hasattr(obj, "_asdict"): # detect namedtuple
        return OrderedDict(zip(obj._fields, (namedtuple_asdict(item) for item in obj)))
    elif isinstance(obj, str): # iterables - strings
        return obj
    elif hasattr(obj, "keys"): # iterables - mapping
        return OrderedDict(zip(obj.keys(), (namedtuple_asdict(item) for item in obj.values())))
    elif hasattr(obj, "__iter__"): # iterables - sequence
        return type(obj)((namedtuple_asdict(item) for item in obj))
    else: # non-iterable cannot contain namedtuples
        return obj

# ....
app = Flask(__name__)

Category = namedtuple(
'Category',
['name', 'priority', 'menus', 'has_next_level']
)

MenuItem = namedtuple(
    'MenuItem',
    ['title', 'url', 'target', 'id', 'parent_id', 'object_type']
)

# --- Sample input value render_template()
menu_content = {
    50: {
        'AGENTS': Category(
            name='AGENTS', 
            priority=1, 
            menus=[
                MenuItem(
                    title='Agent2', 
                    url='/monitoring/dashboard/6/agent/2', 
                    target=None, 
                    id=2, 
                    parent_id=None, 
                    object_type='agent'
                    ), 
                MenuItem(
                    title='Postgres Enterprise Manager Host', 
                    url='/monitoring/dashboard/6/agent/1', 
                    target=None, 
                    id=1, 
                    parent_id=None, 
                    object_type='agent'
                )], 
            has_next_level=True
        )
    }
}

# Convert the dict of dict of namedtuple etc. to JSON string
menu_content_JSONified = json.dumps(namedtuple_asdict(menu_content))

output = None
with app.app_context():
    output = render_template_string(
        '{{ menu_content|safe }}',
        menu_content=menu_content_JSONified
    )

print(output)

输出:

{"50": 
    {"AGENTS": 
        {"name": "AGENTS", 
        "priority": 1, 
        "menus": [
            {"title": "Agent2", "url": "/monitoring/dashboard/6/agent/2", "target": null, "id": 2, "parent_id": null, "object_type": "agent"}, 
            {"title": "Postgres Enterprise Manager Host", "url": "/monitoring/dashboard/6/agent/1", "target": null, "id": 1, "parent_id": null, "object_type": "agent"}], 
        "has_next_level": true
        }
    }
}

通过调用 json.dumps 你会得到一个 JSON 字符串。如果你只使用 namedtuple_asdict 你会得到一个 OrderedDict.

解释:

主要问题是 json.JSONEncoder 默认不支持您要 JSONify 的对象(参见此处的转换 table:https://docs.python.org/3.6/library/json.html#py-to-json-table ).

首先,对象 menu_contentdict of namedtupledict

其次,这个namedtuple包含一个str、一个int、一个list和一个bool

第三,list包含2个namedtuple

所以我们必须找到一种方法来告诉如何正确地将这种类型的结构转换为JSON。

我们可以扩展 json.JSONEncoder(如文档中的 ComplexEncoder 示例 (https://docs.python.org/3.6/library/json.html),或者创建一个可以处理该问题的函数。

在这里,我们使用函数来检测每个对象的类型并将其转换以获得正确的类型。

此功能归功于@MisterMiyagi,他将其发布在此处:Serializing a nested namedtuple into JSON with Python >= 2.7。 我只是对 Python 3.

做了一点修改

您注意到您的 namedtuple 已转换为 list:那是因为 json.JSONEncoder 使用的转换 table(请参阅上面的 link)告诉将 Python 的 lists 和 tuples 转换为 JSON 数组。

并且 namedtupletuple 的子类,因此您的 namedtuple 被转换为 JSON 数组。

示例:

menu_item = MenuItem(
                    title='Agent2', 
                    url='url2', 
                    target=None, 
                    id=2, 
                    parent_id=None, 
                    object_type='agent'
                    )

menu_item_json_dumps = json.dumps(menu_item)
print(menu_item_json_dumps)
# OUTPUT: ["Agent2", "url2", null, 2, null, "agent"]

menu_item_as_dict_json_dumps = json.dumps(menu_item._asdict())
print(menu_item_as_dict_json_dumps)
# OUTPUT: {"title": "Agent2", "url": "url2", "target": null, "id": 2, "parent_id": null, "object_type": "agent"}

您可以在@martineau 的回答中找到更多相关信息:Why doesn't JSONEncoder work for namedtuples?

调用上面的函数会在需要的时候调用_asdict()。 您可以调整它以便 examlpe 跳过一些 None 值或其他值。