08_jackson-3

第七十四章 Jackson库之三 (解析和生成XML文档)

在前两章我们介绍了Jackson库解析和生成JSON文档的使用方法。在本章中,我们继续介绍如何使用Jackson库解析和生成XML文档。因为Jackson对象映射(Object Mapping)的方法使用非常方便,所以,我们本章重点介绍这种方法。

如果使用Maven管理项目,需要将如下依赖关系加入项目的pom.xml文件中。在解析和生成XML文档时,Jackson代码库依赖于stax2-api和jackson-module-jaxb-annotation代码库。所以,我们需要把它们也添加进来。

<dependency>
  <groupId>org.codehaus.woodstox</groupId>
  <artifactId>stax2-api</artifactId>
  <version>4.2</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-jaxb-annotations</artifactId>
  <version>2.11.1</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.11.1</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.11.1</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.11.1</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
  <version>2.11.1</version>
</dependency>

如果使用Gradle管理项目,需要将如下依赖加入项目的build.gradle文件中。

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.codehaus.woodstox:staw2-api:4.2'
  implementation 'com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.11.1'
  implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1'
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1'
  implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
  implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.1'
}

1 XML文档生成

1.1 基本使用方法

XML文档生成的方法与生成JSON文档非常相似。在解析和生成XML时,我们需要使用XmlMapper类。XmlMapper类继承自ObjectMapper类,所以,在解析和生成XML时使用的函数接口是一致的。

例如,我们计划生成如下的XML字符串,根节点的名称为Person。根节点包含name和age两个节点。

<Person>
  <name>David</name>
  <age>22</age>
</Person>

为了生成上述的XML文档,我们可以创建一个Person类。Person类中包含name和age两个成员变量。在main()函数中,我们在创建了新的XmlMapper对象和Person对象之后,调用writeValueAsString()方法将Person对象转换为相应的XML文档。

import java.io.IOException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

public class Person {
    private String name = null;
    private Integer age = null;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }

    public static void main(String[] args) {
        try {
            XmlMapper mapper = new XmlMapper();
            Person person = new Person("David", 22);

            // 生成XML字符串
            System.out.println(mapper.writeValueAsString(person));
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

上述的程序输出为:

<Person xmlns=""><name>David</name><age>22</age></Person>

1.2 多级XML文档生成

Jackson库也支持生成多级XML文档。例如,在如下的XML文档中,根节点Person包含department节点;Department节点包含name和code节点。

<Person>
  <name>David</name>
  <age>22</age>
  <department>
    <name>Computer Science</name>
    <code>CS</code>
  </department>
</Person>

为了生成上述的XML文档,我们可以在Person类中添加一个Department类对象的成员变量。

import java.io.IOException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

public class Department {
    private String name = null;
    private String code = null;

    public Department(String name, String code) {
        this.name = name;
        this.code = code;
    }

    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
}

public class Person {
    private String name = null;
    private Integer age = null;
    private Department department = null;

    public Person(String name, Integer age, Department department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }

    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Department getDepartment() {
        return this.department;
    }
    public void setDepartment(Department department) {
        this.department = department;
    }

    public static void main(String[] args) {
        try {
            XmlMapper mapper = new XmlMapper();
            Person person = new Person("David", 22, new Department("Computer Science", "CS"));

            // 生成XML字符串
            System.out.println(mapper.writeValueAsString(person));
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

上述的程序输出如下。因为这个XML字符串是以“紧凑”格式输出的。读者可将其拷贝至XML格式化页面,格式化之后阅读其结构和内容。

<Person xmlns=""><name>David</name><age>22</age><department><name>Computer Science</name><code>CS</code></department></Person>

1.3 Jackson标注的用法

1.3.1 属性名称

如果无法使得程序中的变量和XML文档中标签的名称保持一致的话,可以使用标签@JacksonXmlProperty来指定标签名称。例如,如果我们将标签名称name和age修改为Name和Age(首字母大写),我们可以在变量的声明处添加标注JacksonXmlProperty。

import java.io.IOException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

public class Person {
    @JacksonXmlProperty(localName = "Name")
    private String name = null;

    @JacksonXmlProperty(localName = "Age")
    private Integer age = null;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }

    public static void main(String[] args) {
        try {
            XmlMapper mapper = new XmlMapper();
            Person person = new Person("David", 22);

            // 生成XML字符串
            System.out.println(mapper.writeValueAsString(person));
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

上述的程序输出为:

<Person xmlns=""><Name>David</Name><Age>22</Age></Person>

1.3.2 声明属性

标签@JacksonXmlProperty还能用于声明XML标签的属性。例如,我们在下面的XML文档中添加一个属性ns。

<Person ns="namespace">
  <name>David</name>
  <age>22</age>
</Person>

在Person类中,新增一个成员变量ns,并使用标注@JacksonXmlProperty来标识这个变量是XML标签的一个属性(Attribute)。

import java.io.IOException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

public class Person {
    @JacksonXmlProperty(isAttribute = true)
    private String ns = null; // 新增属性ns

    private String name = null;
    private Integer age = null;

    public Person(String ns, String name, Integer age) {
        this.ns = ns;
        this.name = name;
        this.age = age;
    }

    // Getters and Setters
    public String getNs() {
        return ns;
    }
    public void setNs(String ns) {
        this.ns = ns;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }

    public static void main(String[] args) {
        try {
            XmlMapper mapper = new XmlMapper();
            Person person = new Person("namespace", "David", 22);

            // 生成XML字符串
            System.out.println(mapper.writeValueAsString(person));
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

上述的程序输出为:

<Person xmlns="" ns="namespace"><name>David</name><age>22</age></Person>

1.3.3 多个数据的生成

JSON文档不同的是,XML文档不支持数组类型。但是,一个XML元素可以包含多个相同名字的标签。例如,在如下的XML文档中,根节点可能包含两个phone节点来标识这个Person对象包含两个电话号码。

<Person>
  <name>David</name>
  <age>22</age>
  <phone>13900000000</phone>
  <phone>13901234567</phone>
</Person>

在源代码中,如果我们使用标注@JacksonXmlElementWrapper来标识phone成员变量,那么,Jackson会将链表phone中的数据一一列出,放在节点Person里面。如果useWrapping的值为true的话,Jackson会在phone节点上额外添加一个节点phone,以保持内容的等级结构。如下所示:

<Person>
  <name>David</name>
  <age>22</age>
  <phone>
    <phone>13900000000</phone>
    <phone>13901234567</phone>
  </phone>
</Person>

这与我们的目标XML文档的结构不一致,因此,我们需要将useWrapping设置为false。

import java.util.List;
import java.util.Arrays;
import java.io.IOException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;

public class Person {
    private String name = null;
    private Integer age = null;

    @JacksonXmlElementWrapper(useWrapping = false)  // 设置为false
    private List<String> phone = null;

    public Person(String name, Integer age, List<String> phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public List<String> getPhone() {
        return this.phone;
    }
    public void setPhone(List<String> phone) {
        this.phone = phone;
    }

    public static void main(String[] args) {
        try {
            XmlMapper mapper = new XmlMapper();
            Person person = new Person("David", 22, Arrays.asList("13900000000", "13901234567"));

            // 生成XML字符串
            System.out.println(mapper.writeValueAsString(person));
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

上述程序的输出如下。读者可将其拷贝至XML格式化页面,格式化之后阅读其结构和内容。

<Person xmlns=""><name>David</name><age>22</age><phone>13900000000</phone><phone>13901234567</phone></Person>

2 XML文档解析

XML解析的过程是XML生成的逆过程。因此,在定义了数据类之后,就能很容易的将XML字符串转换成Java对象。在XML解析过程中,标注的使用方法与XML生成过程中的使用方法保持一致。例如:

import java.io.IOException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

public class Person {
    private String name = null;
    private Integer age = null;
    
    public Person(){
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    // Getters and Setters
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }

    public static void main(String[] args) {
        try {
            XmlMapper mapper = new XmlMapper();
            String xmlStr = "<Person><name>David</name><age>22</age></Person>";

            // 解析XML字符串
            Person person = mapper.readValue(xmlStr, Person.class);
            System.out.println("name=" + person.getName());
            System.out.println("age=" + person.getAge());
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

上述的程序输出为:

name=David
age=22

3 结语

本章介绍了如何使用Jackson对象映射的方法解析和生成XML文档。因为Jackson库的底层提供了灵活的、可扩展的接口,Jackson自身能够适配解析JSON、XML、YAML和一些常用的文档格式,并且为应用程序提供了简洁的序列化和反序列化接口。使用Jackson代码库,能帮助开发人员在几行代码中完成文档的格式转换,非常方便。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.