Jinja map 已写 default=none,缺失字段仍报 Undefined
Jinja 3.1.6 的 map 属性读取用 None 表示未提供默认值,显式 default=none 仍留下 Undefined。用后续 default 过滤器处理缺失,保留 0、False 与空字符串。
模板想把缺失的 foo 字段变成空值,写了 map(attribute="foo", default=none),结果仍是 Undefined;在严格未定义值环境或 Ansible 中,进一步使用结果时会报类似 object of type 'dict' has no attribute 'foo'。Jinja 3.1.6 对 None 的这条处理与 0、False 等默认值不同。
none 是 Jinja 中的空值字面量,不是字符串。把它写成 "None" 会改变结果类型,只会得到四个字符,不能解决需要真正空值的场景。
None 恰好也是内部的“没有默认值”
map 的属性读取会把默认值交给 make_attrgetter()。3.1.6 中,只有满足 default is not None 且读取结果是 Undefined 时,才会替换成默认值。因此明确传入 none,仍无法进入替换分支。
这不表示所有假值都失效,也不表示字典里已有 foo: None 就不能读取。问题是“键缺失,需要用 None 补上”,与“键存在,值本来就是 None”不同。
对于简单字段路径,可以先取得属性,再逐个替换 Undefined:
{{ rows | map(attribute="foo") | map("default", none) | list }}
第二个 map 调用的是 default 过滤器;它能接收真正的 None 作为替代值。list 将惰性结果物化,便于后续使用;若这里只是 HTML 文本输出,列表的展示样式不是 JSON 序列化格式。
例如输入由 {}、{"foo": 0}、{"foo": False}、{"foo": ""} 和 {"foo": None} 组成时,预期仅缺失的第一项被补成 None,其余四项保留原值。不要给 default 增加 true 作为第二参数;那会连 0、False 和空字符串也一起替换。
字段路径和运行环境仍有边界
上面的模板针对单层 foo。若改成 profile.foo,中间的 profile 自身缺失,在默认 Undefined 或 StrictUndefined 配置下可能在到达最后一层之前就报错。不能把同一条表达式推广成任意嵌套对象都安全。
嵌套结构形状复杂时,更适合在模板之前规范化输入:逐项核对是否为预期映射、哪些层级允许缺失,再按业务规则生成字段。只想处理缺失值,不应把所有异常都吞成 None;权限、类型和真实属性访问错误应保留。
Ansible 对数据标记和模板结果还有自己的处理,不同入口不一定与普通 Jinja 的字符串渲染完全相同。先在实际任务或应用里核对结果类型,再决定交给 JSON 编码、HTML 输出还是下一步模块;不要只看日志里出现了 None 几个字符。
验收应包含缺失键、显式 None、0、False、空字符串和正常值。确认结果顺序、长度和原有值没有变化,并检查后续消费者能接受空值。关闭严格未定义值检查会掩盖其他字段错误,不是这条 map 表达式的必要修复。


