- 将树形 Java 对象序列化为 JSON 字符串。
- 将 JSON 字符串反序列化为树形 Java 对象。
下面我将通过一个完整的、分步的示例来详细讲解这个过程,我们将使用 Jackson 库,它是 Java 生态中最流行、功能最强大的 JSON 处理库。
第一步:添加 Jackson 依赖
确保你的项目中包含了 Jackson 的核心库,如果你使用 Maven (pom.xml),添加以下依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version> <!-- 使用最新的稳定版本 -->
</dependency>
如果你使用 Gradle (build.gradle),添加:
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
第二步:设计树形 Java 对象模型
我们需要定义几个类来表示树的结构,一个典型的树节点包含:
- 自身数据 (ID, 名称)
- 子节点列表 (一个
List<Node>)
基础节点类 (TreeNode.java)
这是一个通用的树节点类,可以用于构建各种树形结构。
import java.util.List;
// 使用 @JsonTypeInfo 和 @JsonSubTypes 来处理多态,后面会解释
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Department.class, name = "department"),
@JsonSubTypes.Type(value = Employee.class, name = "employee")
})
public abstract class TreeNode {
private String id;
private String name;
private List<TreeNode> children;
// 构造函数、Getter 和 Setter 是必须的
public TreeNode() {}
public TreeNode(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<TreeNode> getChildren() {
return children;
}
public void setChildren(List<TreeNode> children) {
this.children = children;
}
@Override
public String toString() {
return "TreeNode{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", children=" + children +
'}';
}
}
具体节点子类 (Department.java 和 Employee.java)
为了演示更复杂的场景,我们创建两个具体的子类:Department (部门) 和 Employee (员工),它们都继承自 TreeNode。
Department.java
public class Department extends TreeNode {
public Department() {
super();
}
public Department(String id, String name) {
super(id, name);
}
}
Employee.java
public class Employee extends TreeNode {
private String position;
public Employee() {
super();
}
public Employee(String id, String name, String position) {
super(id, name);
this.position = position;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
@Override
public String toString() {
return "Employee{" +
"id='" + getId() + '\'' +
", name='" + getName() + '\'' +
", position='" + position + '\'' +
", children=" + getChildren() +
'}';
}
}
@JsonTypeInfo 和 JsonSubTypes 的说明:
这两个注解用于处理多态,当序列化或反序列化一个 List<TreeNode> 时,Jackson 需要知道列表中的具体对象是 Department 还是 Employee。
@JsonTypeInfo: 在 JSON 中添加一个字段(这里是type)来标识对象的实际类型。@JsonSubTypes: 告诉 Jacksontype字段的值与具体 Java 类的对应关系。
第三步:序列化(Java 对象 -> JSON)
我们创建一个树形结构的 Java 对象,并将其转换为 JSON 字符串。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.Arrays;
import java.util.List;
public class TreeToJsonExample {
public static void main(String[] args) {
// 1. 创建树形结构
// CEO
Employee ceo = new Employee("1", "Alice", "CEO");
// 技术部
Department techDept = new Department("2", "Technology");
// 员工 Bob (CTO)
Employee cto = new Employee("3", "Bob", "CTO");
// 员工 Carol (Developer)
Employee carol = new Employee("4", "Carol", "Developer");
techDept.setChildren(Arrays.asList(cto, carol));
// 市场部
Department marketDept = new Department("5", "Marketing");
// 员工 David (Manager)
Employee david = new Employee("6", "David", "Manager");
marketDept.setChildren(Arrays.asList(david));
// CEO 管理两个部门
ceo.setChildren(Arrays.asList(techDept, marketDept));
// 2. 创建 ObjectMapper 实例
ObjectMapper objectMapper = new ObjectMapper();
// 美化输出,格式化 JSON
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
// 3. 将 Java 对象序列化为 JSON 字符串
String jsonString = objectMapper.writeValueAsString(ceo);
// 4. 打印结果
System.out.println("生成的 JSON:");
System.out.println(jsonString);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
输出结果:
生成的 JSON:
{
"type" : "employee",
"id" : "1",
"name" : "Alice",
"position" : "CEO",
"children" : [ {
"type" : "department",
"id" : "2",
"name" : "Technology",
"children" : [ {
"type" : "employee",
"id" : "3",
"name" : "Bob",
"position" : "CTO",
"children" : null
}, {
"type" : "employee",
"id" : "4",
"name" : "Carol",
"position" : "Developer",
"children" : null
} ]
}, {
"type" : "department",
"id" : "5",
"name" : "Marketing",
"children" : [ {
"type" : "employee",
"id" : "6",
"name" : "David",
"position" : "Manager",
"children" : null
} ]
} ]
}
可以看到,JSON 结构清晰地反映了我们的树形数据,type 字段帮助 Jackson 区分了 department 和 employee。
第四步:反序列化(JSON -> Java 对象)
我们将上面生成的 JSON 字符串转换回我们的 Java 对象树。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
public class JsonToTreeExample {
public static void main(String[] args) {
// 上一步生成的 JSON 字符串
String jsonString = "{\n" +
" \"type\" : \"employee\",\n" +
" \"id\" : \"1\",\n" +
" \"name\" : \"Alice\",\n" +
" \"position\" : \"CEO\",\n" +
" \"children\" : [ {\n" +
" \"type\" : \"department\",\n" +
" \"id\" : \"2\",\n" +
" \"name\" : \"Technology\",\n" +
" \"children\" : [ {\n" +
" \"type\" : \"employee\",\n" +
" \"id\" : \"3\",\n" +
" \"name\" : \"Bob\",\n" +
" \"position\" : \"CTO\",\n" +
" \"children\" : null\n" +
" }, {\n" +
" \"type\" : \"employee\",\n" +
" \"id\" : \"4\",\n" +
" \"name\" : \"Carol\",\n" +
" \"position\" : \"Developer\",\n" +
" \"children\" : null\n" +
" } ]\n" +
" }, {\n" +
" \"type\" : \"department\",\n" +
" \"id\" : \"5\",\n" +
" \"name\" : \"Marketing\",\n" +
" \"children\" : [ {\n" +
" \"type\" : \"employee\",\n" +
" \"id\" : \"6\",\n" +
" \"name\" : \"David\",\n" +
" \"position\" : \"Manager\",\n" +
" \"children\" : null\n" +
" } ]\n" +
" } ]\n" +
"}";
// 1. 创建 ObjectMapper 实例
ObjectMapper objectMapper = new ObjectMapper();
try {
// 2. 将 JSON 字符串反序列化为根节点对象
// 因为 JSON 的根节点是 "employee" 类型,所以它会正确地映射到 Employee 类
Employee ceo = objectMapper.readValue(jsonString, Employee.class);
// 3. 打印结果,验证树形结构是否正确重建
System.out.println("重建的 Java 对象树:");
printTree(ceo, 0);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 一个简单的递归方法来打印树结构
* @param node 树节点
* @param level 层级,用于缩进
*/
public static void printTree(TreeNode node, int level) {
if (node == null) {
return;
}
StringBuilder indent = new StringBuilder();
for (int i = 0; i < level; i++) {
indent.append(" ");
}
System.out.println(indent + "- " + node.getName() + " (ID: " + node.getId() + ")");
if (node instanceof Employee) {
Employee emp = (Employee) node;
if (emp.getPosition() != null) {
System.out.println(indent + " Position: " + emp.getPosition());
}
}
if (node.getChildren() != null) {
for (TreeNode child : node.getChildren()) {
printTree(child, level + 1);
}
}
}
}
输出结果:
重建的 Java 对象树:
- Alice (ID: 1)
Position: CEO
- Technology (ID: 2)
- Bob (ID: 3)
Position: CTO
- Carol (ID: 4)
Position: Developer
- Marketing (ID: 5)
- David (ID: 6)
Position: Manager
可以看到,原始的树形结构被完美地重建了。
总结与最佳实践
- 选择合适的库:Jackson 是事实上的标准,功能强大且性能优异。
- 设计清晰的模型:为树节点定义一个基类(如
TreeNode),然后根据具体需求创建子类,这使得你的模型更具扩展性。 - 处理多态:当你的树中包含不同类型的节点时(如部门、员工、文件等),必须使用
@JsonTypeInfo和@JsonSubTypes注解,这是最关键的一步。 - 递归结构:树形结构的本质是递归,在序列化和反序列化过程中,Jackson 会自动处理这种递归关系。
- 可读性:使用
ObjectMapper.enable(SerializationFeature.INDENT_OUTPUT)可以让输出的 JSON 更易于阅读和调试。 - 循环引用:如果你的树结构中可能存在循环引用(子节点引用了父节点),默认情况下 Jackson 会抛出异常,你需要配置
ObjectMapper来处理这种情况,objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 处理循环引用 objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 使用 SimpleModule 来处理循环引用 SimpleModule module = new SimpleModule(); module.addSerializer(TreeNode.class, new StdSerializer<TreeNode>(TreeNode.class) { @Override public void serialize(TreeNode value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 自定义序列化逻辑以避免循环 gen.writeStartObject(); gen.writeStringField("id", value.getId()); gen.writeStringField("name", value.getName()); // ... 其他字段 // 不序列化 children 或者做特殊处理 gen.writeEndObject(); } }); objectMapper.registerModule(module);更简单的方法是使用
@JsonManagedReference和@JsonBackReference,但对于复杂的树结构,自定义序列化器可能更灵活。
