如何利用框架将带JSONPath的Map转换为嵌套JSON字符串?
扁平化Map转嵌套JSON的框架方案
问题场景
我有如下键值对形式的Map:
{ "key1": "value1", "list[0].key1": "listkeyvalue1", "list[0].key2": "listkeyvalue2", "list[1].key1": "listkeyvalue1", "list[1].key2": "listkeyvalue3" }
使用Jackson的ObjectMapper执行以下代码:
new ObjectMapper().writeValueAsString(map);
得到的是扁平化JSON:
{ "key1": "value1", "list[0].key1": "listkeyvalue1", "list[0].key2": "listkeyvalue2", "list[1].key1": "listkeyvalue1", "list[1].key2": "listkeyvalue3" }
但期望生成嵌套结构的JSON:
{ "key1": "value1", "list": [ { "key1": "listkeyvalue1", "key2": "listkeyvalue2" }, { "key1": "listkeyvalue1", "key2": "listkeyvalue3" } ] }
想知道有没有现成框架可以实现这个转换,不用自己编写逻辑。
可用框架方案
不用自己写路径解析逻辑,有几个现成工具可以直接实现这个转换:
1. Apache Commons Configuration
这个库原生支持解析带点分隔和数组索引的键,把扁平化Map转成嵌套结构。步骤很简单:
- 用
MapConfiguration包装你的扁平化Map - 借助
NodeModel将配置转为树形节点结构 - 把树形节点转成普通Map后,再用Jackson序列化即可
示例代码:
import org.apache.commons.configuration2.MapConfiguration; import org.apache.commons.configuration2.tree.ImmutableNode; import org.apache.commons.configuration2.tree.NodeModel; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; public class FlatToNested { public static void main(String[] args) throws Exception { // 初始化扁平化Map Map<String, String> flatMap = new HashMap<>(); flatMap.put("key1", "value1"); flatMap.put("list[0].key1", "listkeyvalue1"); flatMap.put("list[0].key2", "listkeyvalue2"); flatMap.put("list[1].key1", "listkeyvalue1"); flatMap.put("list[1].key2", "listkeyvalue3"); // 转换为嵌套结构 MapConfiguration config = new MapConfiguration(flatMap); NodeModel model = NodeModel.createModel(config); ImmutableNode root = model.getRootNode(); // 将ImmutableNode转为可序列化的Map Map<String, Object> nestedMap = nodeToMap(root); // 生成嵌套JSON ObjectMapper mapper = new ObjectMapper(); System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nestedMap)); } private static Map<String, Object> nodeToMap(ImmutableNode node) { Map<String, Object> map = new HashMap<>(); for (ImmutableNode child : node.getChildren()) { if (child.getChildren().isEmpty()) { map.put(child.getNodeName(), child.getValue()); } else { // 处理数组(节点名称相同的情况) if (map.containsKey(child.getNodeName())) { Object existing = map.get(child.getNodeName()); if (!(existing instanceof java.util.List)) { java.util.List<Object> list = new java.util.ArrayList<>(); list.add(existing); list.add(nodeToMap(child)); map.put(child.getNodeName(), list); } else { ((java.util.List<Object>) existing).add(nodeToMap(child)); } } else { map.put(child.getNodeName(), nodeToMap(child)); } } } return map; } }
2. Spring Binder(Spring生态下首选)
如果你的项目基于Spring或Spring Boot,直接用Binder类就能把扁平化键值对绑定到对应的实体类,序列化实体类就得到嵌套JSON,完全不用自己处理路径:
首先定义对应实体类:
public class DataModel { private String key1; private List<Item> list; // getter、setter public static class Item { private String key1; private String key2; // getter、setter } }
然后用Binder绑定并序列化:
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; public class SpringBinderExample { public static void main(String[] args) throws Exception { Map<String, String> flatMap = new HashMap<>(); flatMap.put("key1", "value1"); flatMap.put("list[0].key1", "listkeyvalue1"); flatMap.put("list[0].key2", "listkeyvalue2"); flatMap.put("list[1].key1", "listkeyvalue1"); flatMap.put("list[1].key2", "listkeyvalue3"); MutablePropertySources sources = new MutablePropertySources(); sources.addFirst(new MapPropertySource("flatMap", flatMap)); DataModel dataModel = Binder.get(sources).bind("", DataModel.class).get(); ObjectMapper mapper = new ObjectMapper(); System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(dataModel)); } }
3. Jackson JsonPointer(轻量实现)
Jackson的JsonPointer可以定位JSON节点,你可以遍历扁平化Map的键,用JsonPointer在空的JsonNode上逐个设置值,最后序列化这个JsonNode。虽然需要写一点遍历代码,但不需要自己解析路径语法:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.HashMap; import java.util.Map; public class JacksonJsonPointerExample { public static void main(String[] args) throws Exception { Map<String, String> flatMap = new HashMap<>(); flatMap.put("key1", "value1"); flatMap.put("list[0].key1", "listkeyvalue1"); flatMap.put("list[0].key2", "listkeyvalue2"); flatMap.put("list[1].key1", "listkeyvalue1"); flatMap.put("list[1].key2", "listkeyvalue3"); ObjectNode rootNode = JsonNodeFactory.instance.objectNode(); ObjectMapper mapper = new ObjectMapper(); for (Map.Entry<String, String> entry : flatMap.entrySet()) { // 将"list[0].key1"转为JSONPointer格式"/list/0/key1" String pointerStr = "/" + entry.getKey().replace("[", "/").replace("]", "").replace(".", "/"); rootNode.at(pointerStr).set(mapper.convertValue(entry.getValue(), JsonNode.class)); } System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode)); } }
内容的提问来源于stack exchange,提问作者Krwasch
相关产品推荐
相关产品推荐

