英文:
How to recursively Print data from template data Map Object in Freemarker?
问题
我们有一个需求,需要将类型为 Map<String,List> 的对象传递给 Freemarker 模板。这里的问题是列表内部的对象可以是 List、Map、自定义对象,或者只是一个简单的字符串。列表和映射类型可以进一步嵌套,类似于以下示例:
Map<String,Object> templateData = new HashMap<>();
templateData.put("complexKey","ABC");
// 或者
templateData.put("complexKey",new ArrayList<String>());
// 或者
templateData.put("complexKey",new ArrayList<Map<String,List<String>>>());
我需要找到一种方法来识别对象的类型,并对其应用一些递归解决方案,直到找到适合打印的对象。
我想知道是否有办法可以在 Freemarker 中直接实现这一点,或者通过为 Freemarker 的任何类/接口提供自定义实现,或者通过一些配置更改来实现这一点。
英文:
We have a requirement where we need to pass object of type Map<String,List> to freemarker template. Here the issue is the Object inside a list can be a List, a Map or custom object or just a simple string. List and Map type can be further nested.something like below.
Map<String,Object> templateData = new HashMap<>();
templateData.put("complexKey","ABC");
//or
templateData.put("complexKey",new List<String>());
//or
templateData.put("complexKey",new List<Map<String,List<String>>>());
I need to find a way to identify the type of Object and apply some recursive solution until I find the suitable object to print.
I need to know if there is a way we can achieve this in free marker directly or through providing custom implementation of any class/interface from freemarker or through some configuration changes.
答案1
得分: 0
我真的对FreeMarker一无所知,但如果你可以通过纯粹的Java来处理它,你可以尝试这样做:
package com.jdluke.treewalker;
import java.util.*;
import java.util.function.Consumer;
public class TreeWalker {
public static void main(String[] args) {
Map<String, Object> mainMap = new HashMap();
mainMap.put("stringNode", "This is just a String");
mainMap.put("listNode", Arrays.asList("one", "two"));
Map mapNode1 = new HashMap();
Map mapNode2 = new HashMap();
Map mapNode3 = new HashMap();
mapNode1.put("mapnode1.1", "first element");
mapNode1.put("mapnode1.2", "second element");
mapNode2.put("mapnode2.1", Arrays.asList("three", "four"));
mapNode3.put("mapnode3.1", "map node 3, element 1");
mapNode3.put("mapnode3.2", "map node 3, element 2");
mainMap.put("listNode2", Arrays.asList(mapNode1, mapNode2));
mainMap.put("mapNode", mapNode3);
int count = 0;
walk(mainMap, (node) -> System.out.println("访问 " + node.toString()));
}
public static void walk(Map node, Consumer lambda) {
System.out.println("Map对象: " + node);
node.forEach((k, v) -> handleNode(v, lambda));
}
public static void walk(Collection node, Consumer lambda) {
System.out.println("可迭代对象: " + node);
node.forEach(v -> handleNode(v, lambda));
}
public static void walk(Object catchall, Consumer lambda) {
System.out.println("通用对象: " + catchall);
lambda.accept(catchall);
}
private static void handleNode(Object v, Consumer lambda) {
System.out.println("处理类型为" + v.getClass().toString() + "的对象");
lambda.accept(v);
if (v instanceof Collection) {
walk((Collection) v, lambda);
} else if (v instanceof Map) {
walk((Map) v, lambda);
} else {
walk(v, lambda);
}
}
}
这里发生的情况是,不同重载的“walk”方法有各自处理混合树中当前位置的方式。Lambda表达式(可以收集统计数据、打印内容等)应用于所有节点。如果需要,可以通过为每个节点都提供前处理和后处理的Consumer来增强这个过程。
由于我们在“处理”对象时丢失了强制转换的信息,我们需要在handleNode中重新注入这些信息。我本来希望能有更简洁的方法,但可能需要更多的咖啡来思考。
英文:
I really don't know anything about freemarker, but if you can get your hooks into it to process via straight Java you can try this:
package com.jdluke.treewalker;
import java.util.*;
import java.util.function.Consumer;
public class TreeWalker {
public static void main(String[] args) {
Map<String, Object> mainMap = new HashMap();
mainMap.put("stringNode", "This is just a String");
mainMap.put("listNode", Arrays.asList("one", "two"));
Map mapNode1 = new HashMap();
Map mapNode2 = new HashMap();
Map mapNode3 = new HashMap();
mapNode1.put("mapnode1.1", "first element");
mapNode1.put("mapnode1.2", "second element");
mapNode2.put("mapnode2.1", Arrays.asList("three", "four"));
mapNode3.put("mapnode3.1", "map node 3, element 1");
mapNode3.put("mapnode3.2", "map node 3, element 2");
mainMap.put("listNode2", Arrays.asList(mapNode1, mapNode2));
mainMap.put("mapNode", mapNode3);
int count = 0;
walk(mainMap, (node) -> System.out.println("Visiting " + node.toString()));
}
public static void walk(Map node, Consumer lambda) {
System.out.println("Map object: " + node);
node.forEach((k, v) -> handleNode(v, lambda));
}
public static void walk(Collection node, Consumer lambda) {
System.out.println("Iterable object: " + node);
node.forEach(v -> handleNode(v, lambda));
}
public static void walk(Object catchall, Consumer lambda) {
System.out.println("Catchall object: " + catchall);
lambda.accept(catchall);
}
private static void handleNode(Object v, Consumer lambda) {
System.out.println("Handling object of type" + v.getClass().toString());
lambda.accept(v);
if (v instanceof Collection) {
walk((Collection) v, lambda);
} else if (v instanceof Map) {
walk((Map) v, lambda);
} else {
walk(v, lambda);
}
}
}
What's happening here is that the various overloaded 'walk' methods have their own ways of dealing with their current location in the mixed tree. The lambda (which can gather stats, print stuff, or whatever) is applied to all nodes. This could be enhanced if necessary by having both a pre- and a post- processing Consumer for each node.
Since we lose cast information when we 'handle' an object we need to sort of reinject that in handleNode. I was hoping for something cleaner but may need more coffee.
答案2
得分: 0
在模板内部,根据我所看到的内容,似乎是可行的。
FreeMarker 宏和函数支持递归。
要检查值的类型,您可以使用 value?is_sequence
(用于 List
),value?is_hash_ex
(用于 Map
),以及 ?is_string
(用于 String
)。(实际上,这取决于在 Configuration
内部设置的 ObjectWrapper
,但在几乎所有真实世界的配置中,这将起作用。)
英文:
Looks doable inside the template based on what I see.
Recursion is supported for FreeMarker macros and functions.
To check the type of a value you can use value?is_sequence
(for List
), value?is_hash_ex
(for Map
), and ?is_string
(for String
). (Actually this depends on the ObjectWrapper
set inside the Configuration
, but on almost all real world configuration this will work.)
专注分享java语言的经验与见解,让所有开发者获益!
评论