FreeMarker循环List检测下一项值 按ID分组输出XML及排错
FreeMarker模板实现采购单分组嵌套XML输出及宏调用排错方案
需求与初始实现问题
- 底层数据源为数据库查询返回的采购明细平铺数据,需输出指定格式嵌套XML:
- 同一采购单ID对应的所有明细归属到同一个
<Purchasing>父节点 - 父节点存储采购单维度字段:
<ID>、<Client>、<Address> - 父节点下挂所有
<PurchasingLine>明细子节点,子节点存储明细维度字段:<Line>、<Item>、<Qty>
- 同一采购单ID对应的所有明细归属到同一个
- 初始实现采用
<#list Purchasing as po>直接遍历平铺明细,尝试通过判断下一条记录的ID、Line值控制<Purchasing>节点闭合,这种写法边界判断逻辑冗余,容易遗漏最后一组数据的闭合标签,维护成本高,不推荐使用。
可落地实现方案
方案1:使用FreeMarker内置?group_by函数实现(无需自定义宏,成本最低)
直接调用FreeMarker自带的分组、排序内置函数完成逻辑,不需要手动处理循环边界,代码样例如下:
<#-- 先按采购单ID、明细行号排序,再按采购单ID分组 --> <#assign groupedPoList = Purchasing?sort_by(["ID", "Line"])?group_by("ID")> <#list groupedPoList as poGroup> <Purchasing> <#-- 采购单维度字段直接取当前分组第一条明细的对应值,同一采购单头字段一致 --> <ID>${poGroup.groupKey}</ID> <Client>${poGroup.items[0].Client}</Client> <Address>${poGroup.items[0].Address1} ${poGroup.items[0].Address2}</Address> <#-- 遍历当前分组下所有明细,生成子节点 --> <#list poGroup.items as line> <PurchasingLine> <Line>${line.Line}</Line> <Item>${line.Item}</Item> <Qty>${line.Qty}</Qty> </PurchasingLine> </#list> </Purchasing> </#list>
注意:如果同一采购单的头字段(Client/Address)在不同明细行存在空值,建议提前在SQL查询层做数据清洗,或在模板中通过
?first取组内第一个非空值,避免输出空节点。
方案2:修正自定义listGroups宏调用方式(支持分组逻辑复用)
原有宏的核心逻辑无需修改,宏通过<#nested>向嵌套块传递两个参数:第一个是分组字段值(采购单ID),第二个是当前分组下的明细序列。修正后的调用代码如下:
<#-- 原有宏定义保持不变,无需新增参数 --> <#macro listGroups items groupField> <#if items?size == 0><#return></#if> <#local sortedItems = items?sort_by(groupField)> <#local groupStart = 0> <#list sortedItems as item> <#if !item?is_first && item[groupField] != lastItem[groupField]> <#local groupEnd = item?index> <#nested lastItem[groupField], sortedItems[groupStart ..< groupEnd]> <#local groupStart = groupEnd> </#if> <#local lastItem = item> </#list> <#local groupEnd = sortedItems?size> <#nested lastItem[groupField], sortedItems[groupStart ..< groupEnd]> </#macro> <#-- 正确调用:分号后仅定义2个变量,按顺序接收宏传递的分组ID、分组明细序列 --> <@listGroups Purchasing "ID"; poId, poLines> <Purchasing> <ID>${poId}</ID> <Client>${poLines[0].Client}</Client> <Address>${poLines[0].Address1} ${poLines[0].Address2}</Address> <#list poLines as line> <PurchasingLine> <Line>${line.Line}</Line> <Item>${line.Item}</Item> <Qty>${line.Qty}</Qty> </PurchasingLine> </#list> </Purchasing> </@listGroups>
三类宏调用报错根因
- 报错:嵌套块直接引用
Client提示值为null或缺失
根因:宏的嵌套块有独立作用域,不会自动继承外层循环的临时变量;且原有宏仅通过<#nested>传递2个参数,没有直接传递Client字段,直接引用时模板会在全局作用域查找该变量,找不到就触发空值报错。 - 报错:追加"Client""Address1"等作为宏入参提示参数数量不匹配
根因:宏定义时仅声明了items、groupField两个形参,调用时传入的额外参数属于多余实参,FreeMarker默认开启严格参数校验,参数数量不匹配时直接抛出错误。如果需要把字段作为宏入参传递,必须先修改宏定义的形参列表。 - 报错:分号后追加
Client等参数提示序列类型无法自动转换为字符串
根因:宏调用时分号后的变量列表,是用来按顺序接收<#nested>传出的返回值的。原有宏仅传出2个值:第一个是字符串类型的分组ID,第二个是序列类型的分组明细列表。如果在分号后定义超过2个接收变量,多余变量会被赋值为null;如果变量顺序写错,把序列类型的明细列表赋值给了本该接收字符串的Client变量,输出时就会触发序列无法转字符串的类型错误。
内容的提问来源于stack exchange,提问作者mjieffect0909
相关产品推荐
相关产品推荐

