杰瑞科技汇

Java树形结构如何高效转JSON?

  1. 将树形 Java 对象序列化为 JSON 字符串
  2. 将 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.javaEmployee.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() +
                '}';
    }
}

@JsonTypeInfoJsonSubTypes 的说明: 这两个注解用于处理多态,当序列化或反序列化一个 List<TreeNode> 时,Jackson 需要知道列表中的具体对象是 Department 还是 Employee

  • @JsonTypeInfo: 在 JSON 中添加一个字段(这里是 type)来标识对象的实际类型。
  • @JsonSubTypes: 告诉 Jackson type 字段的值与具体 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 区分了 departmentemployee


第四步:反序列化(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

可以看到,原始的树形结构被完美地重建了。


总结与最佳实践

  1. 选择合适的库:Jackson 是事实上的标准,功能强大且性能优异。
  2. 设计清晰的模型:为树节点定义一个基类(如 TreeNode),然后根据具体需求创建子类,这使得你的模型更具扩展性。
  3. 处理多态:当你的树中包含不同类型的节点时(如部门、员工、文件等),必须使用 @JsonTypeInfo@JsonSubTypes 注解,这是最关键的一步。
  4. 递归结构:树形结构的本质是递归,在序列化和反序列化过程中,Jackson 会自动处理这种递归关系。
  5. 可读性:使用 ObjectMapper.enable(SerializationFeature.INDENT_OUTPUT) 可以让输出的 JSON 更易于阅读和调试。
  6. 循环引用:如果你的树结构中可能存在循环引用(子节点引用了父节点),默认情况下 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,但对于复杂的树结构,自定义序列化器可能更灵活。

分享:
扫描分享到社交APP
上一篇
下一篇