如何将动态多层级JSON转换为可跨项目复用的Java POJO
动态多层级无固定结构JSON转可复用Java实体方案
不需要为每个JSON结构单独编写POJO、支持任意层级嵌套、可跨项目复用的核心实现思路是递归解析任意类型的JSON值,用通用存储结构代替固定字段定义,以下两种经过生产验证的方案都可以解决@JsonAnySetter、单层级JsonNode无法处理嵌套结构的问题:
方案一:自定义通用动态实体(推荐,易用性最高)
之前用@JsonAnySetter只能处理单层级结构,本质是没有对传入的嵌套Map、List类型的值做二次递归解析。我们可以基于@JsonAnySetter扩展,实现一次编写、所有项目可直接复用的通用实体类,内部自动递归处理嵌套对象、数组类型,不需要针对业务字段做任何硬编码:
import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class DynamicJsonEntity { // 存储所有动态键值对,支持任意键名 private final Map<String, Object> properties = new HashMap<>(); // 全局复用序列化配置,可根据自己的业务需求扩展 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 所有未知键的赋值统一走这个入口 @JsonAnySetter public void set(String key, Object value) { properties.put(key, parseValue(value)); } // 递归解析值类型,支持任意深度嵌套 private Object parseValue(Object value) { if (value instanceof Map) { // 嵌套JSON对象递归转成DynamicJsonEntity实例 DynamicJsonEntity nestedEntity = new DynamicJsonEntity(); ((Map<?, ?>) value).forEach((k, v) -> nestedEntity.set(k.toString(), v)); return nestedEntity; } else if (value instanceof List) { // JSON数组递归处理每一个元素 List<Object> parsedList = new ArrayList<>(); for (Object item : (List<?>) value) { parsedList.add(parseValue(item)); } return parsedList; } // 字符串、数字、布尔值等基本类型直接存储 return value; } // 路径式取值,支持.取对象属性、[index]取数组下标 public Object get(String path) { String[] nodes = path.split("\\."); Object current = this.properties; for (String node : nodes) { if (current == null) return null; if (node.contains("[")) { String fieldName = node.substring(0, node.indexOf("[")); int index = Integer.parseInt(node.substring(node.indexOf("[")+1, node.indexOf("]"))); current = ((Map<?,?>)current).get(fieldName); if (current == null) return null; current = ((List<?>)current).get(index); } else { if (current instanceof DynamicJsonEntity) { current = ((DynamicJsonEntity) current).properties.get(node); } else if (current instanceof Map) { current = ((Map<?,?>)current).get(node); } else { return null; } } } return current; } // JSON字符串转实体的统一入口,全项目直接调用 public static DynamicJsonEntity fromJson(String json) throws Exception { return OBJECT_MAPPER.readValue(json, DynamicJsonEntity.class); } // 按需转成原生Map结构使用 public Map<String, Object> toMap() { return this.properties; } }
针对你给出的示例JSON,使用方式如下:
public class Test { public static void main(String[] args) throws Exception { String json = "{\n" + " \"name\" : \"sahal\",\n" + " \"address\" : [\n" + " {\"state\" : \"FL\"},\n" + " {\"country\" : \"FL\",\"city\" : {\"type\" : \"municipality\",\"value\" : \"California City\"}},\n" + " {\"pin\" : \"87876\"}\n" + " ]\n" + "}"; DynamicJsonEntity entity = DynamicJsonEntity.fromJson(json); // 取普通属性,后续键名改成FirstName直接修改路径参数即可,不需要改类代码 System.out.println(entity.get("name")); // 输出 sahal // 取嵌套数组里的深层属性 System.out.println(entity.get("address[1].city.value")); // 输出 California City System.out.println(entity.get("address[2].pin")); // 输出 87876 } }
这个方案的优势:
- 无业务耦合:类可以直接抽成公共工具包的基础类,所有项目引入就能用,不管JSON的键名怎么改、嵌套多少层、结构怎么调整都不需要修改类代码
- 易用性强:封装了路径式取值方法,不需要手动遍历多层结构做类型转换
- 兼容性好:如果后续某个字段固定需要强类型,直接在类里添加对应字段加
@JsonProperty注解即可,不影响原有动态字段的解析
方案二:通用JSON转Map工具类(零实体封装)
如果不想维护自定义实体类,可以写一个递归解析的工具类,直接把任意JSON转成嵌套的Map/List/基本类型结构,工具类同样可以跨项目复用:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class JsonUtils { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); // 任意JSON转嵌套Map结构入口 public static Map<String, Object> jsonToMap(String json) throws Exception { JsonNode root = OBJECT_MAPPER.readTree(json); return (Map<String, Object>) parseNode(root); } private static Object parseNode(JsonNode node) { if (node.isObject()) { Map<String, Object> map = new HashMap<>(); node.fields().forEachRemaining(entry -> map.put(entry.getKey(), parseNode(entry.getValue()))); return map; } else if (node.isArray()) { List<Object> list = new ArrayList<>(); node.forEach(item -> list.add(parseNode(item))); return list; } else if (node.isTextual()) { return node.asText(); } else if (node.isNumber()) { return node.numberValue(); } else if (node.isBoolean()) { return node.asBoolean(); } return node.isNull() ? null : node.asText(); } }
这个方案的缺点是取嵌套值时需要多层强转,代码可读性差,容易出现类型转换异常,适合临时处理JSON的场景,不适合在核心业务逻辑里大量使用。
内容的提问来源于stack exchange,提问作者Sahal
相关产品推荐
相关产品推荐

