XML结构

XML是可扩展标记语言(eXtensible Markup Language)XML有几个特点:一是纯文本,默认使用UTF-8编码,二是可嵌套,适合表示结构化数据。
XML有固定的结构,首行必定是,可以加上可选的编码。紧接着,如果以类似声明的是文档定义类型(DTD:Document Type Definition),DTD是可选的。接下来是XML的文档内容,一个XML文档有且仅有一个根元素,根元素可以包含任意个子元素,元素可以包含属性,例如,1234567包含一个属性lang="CN",且元素必须正确嵌套。如果是空元素,可以用`表示。<br />由于使用了<>以及引号等标识符,如果内容出现了特殊符号,需要使用&???;表示转义。例如,Java`必须写成:

  1. <name>Java&lt;tm&gt;</name>

常见的特殊字符如下:

| 字符 | 表示 | | —- | —- |

| < | < |

| > | > |

| & | & |

| “ | “ |

| ‘ | ‘ |

格式正确的XML(Well Formed)是指XML的格式是正确的,可以被解析器正常读取。而合法的XML是指,不但XML格式正确,而且它的数据结构可以被DTD或者XSD验证。

XML解析

因为XML是一种树形结构的文档,它有两种标准的解析API:

  • DOM:一次性读取XML,并在内存中表示为树形结构;
  • SAX:以流的形式读取XML,使用事件回调。

    使用Jackson

    前面我们介绍了DOM和SAX两种解析XML的标准接口。但是,无论是DOM还是SAX,使用起来都不直观。
    观察XML文档的结构: ``` <?xml version=”1.0” encoding=”UTF-8” ?> Java核心技术 Cay S. Horstmann 1234567 Java Network
我们发现,它完全可以对应到一个定义好的JavaBean中:

public class Book { public long id; public String name; public String author; public String isbn; public List tags; public String pubDate; }

如果能直接从XML文档解析成一个JavaBean,那比DOM或者SAX不知道容易到哪里去了。<br />幸运的是,一个名叫Jackson的开源的第三方库可以轻松做到XML到JavaBean的转换。我们要使用Jackson,先添加两个Maven的依赖:
com.fasterxml.jackson.dataformat jackson-dataformat-xml 2.10.1 org.codehaus.woodstox woodstox-core-asl 4.4.1
然后,定义好JavaBean,就可以用下面几行代码解析:

InputStream input = Main.class.getResourceAsStream(“/book.xml”); JacksonXmlModule module = new JacksonXmlModule(); XmlMapper mapper = new XmlMapper(module); Book book = mapper.readValue(input, Book.class); System.out.println(book.id); System.out.println(book.name); System.out.println(book.author); System.out.println(book.isbn); System.out.println(book.tags); System.out.println(book.pubDate);

注意到`XmlMapper`就是我们需要创建的核心对象,可以用`readValue(InputStream, Class)`直接读取XML并返回一个JavaBean。运行上述代码,就可以直接从Book对象中拿到数据:

1 Java核心技术 Cay S. Horstmann 1234567 [Java, Network] null

如果要解析的数据格式不是Jackson内置的标准格式,那么需要编写一点额外的扩展来告诉Jackson如何自定义解析。这里我们不做深入讨论,可以参考Jackson的[官方文档](https://github.com/FasterXML/jackson)。
## JSON
前面我们讨论了XML这种数据格式。XML的特点是功能全面,但标签繁琐,格式复杂。在Web上使用XML现在越来越少,取而代之的是JSON这种数据结构。<br />JSON是JavaScript Object Notation的缩写,它去除了所有JavaScript执行代码,只保留JavaScript的对象格式。一个典型的JSON如下:

{ “id”: 1, “name”: “Java核心技术”, “author”: { “firstName”: “Abc”, “lastName”: “Xyz” }, “isbn”: “1234567”, “tags”: [“Java”, “Network”] }

JSON作为数据传输的格式,有几个显著的优点:

- JSON只允许使用UTF-8编码,不存在编码问题;
- JSON只允许使用双引号作为key,特殊字符用`\`转义,格式简单;
- 浏览器内置JSON支持,如果把数据用JSON发送给浏览器,可以用JavaScript直接处理。

因此,JSON适合表示层次结构,因为它格式简单,仅支持以下几种数据类型:

- 键值对:`{"key": value}`
- 数组:`[1, 2, 3]`
- 字符串:`"abc"`
- 数值(整数和浮点数):`12.34`
- 布尔值:`true`或`false`
- 空值:`null`

浏览器直接支持使用JavaScript对JSON进行读写:

// JSON string to JavaScript object: jsObj = JSON.parse(jsonStr);

// JavaScript object to JSON string: jsonStr = JSON.stringify(jsObj);

所以,开发Web应用的时候,使用JSON作为数据传输,在浏览器端非常方便。因为JSON天生适合JavaScript处理,所以,绝大多数REST API都选择JSON作为数据传输格式。<br />现在问题来了:使用Java如何对JSON进行读写?<br />在Java中,针对JSON也有标准的JSR 353 API,但是我们在前面讲XML的时候发现,如果能直接在XML和JavaBean之间互相转换是最好的。类似的,如果能直接在JSON和JavaBean之间转换,那么用起来就简单多了。<br />常用的用于解析JSON的第三方库有:

- Jackson
- Gson
- Fastjson
- ...

注意到上一节提到的那个可以解析XML的浓眉大眼的Jackson也可以解析JSON!因此我们只需要引入以下Maven依赖:
com.fasterxml.jackson.core jackson-databind 2.10.0
就可以使用下面的代码解析一个JSON文件:

InputStream input = Main.class.getResourceAsStream(“/book.json”); ObjectMapper mapper = new ObjectMapper(); // 反序列化时忽略不存在的JavaBean属性: mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); Book book = mapper.readValue(input, Book.class);

核心代码是创建一个`ObjectMapper`对象。关闭`DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`功能使得解析时如果JavaBean不存在该属性时解析不会报错。<br />把JSON解析为JavaBean的过程称为反序列化。如果把JavaBean变为JSON,那就是序列化。要实现JavaBean到JSON的序列化,只需要一行代码:

String json = mapper.writeValueAsString(book);

要把JSON的某些值解析为特定的Java对象,例如`LocalDate`,也是完全可以的。例如:

{ “name”: “Java核心技术”, “pubDate”: “2016-09-01” }

要解析为:

public class Book { public String name; public LocalDate pubDate; }

只需要引入标准的JSR 310关于JavaTime的数据格式定义:
com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.10.0
然后,在创建`ObjectMapper`时,注册一个新的`JavaTimeModule`:

ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());

有些时候,内置的解析规则和扩展的解析规则如果都不满足我们的需求,还可以自定义解析。<br />举个例子,假设`Book`类的`isbn`是一个`BigInteger`:

public class Book { public String name; public BigInteger isbn; }

但JSON数据并不是标准的整形格式:

{ “name”: “Java核心技术”, “isbn”: “978-7-111-54742-6” }

直接解析,肯定报错。这时,我们需要自定义一个`IsbnDeserializer`,用于解析含有非数字的字符串:

public class IsbnDeserializer extends JsonDeserializer { public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { // 读取原始的JSON字符串内容: String s = p.getValueAsString(); if (s != null) { try { return new BigInteger(s.replace(“-“, “”)); } catch (NumberFormatException e) { throw new JsonParseException(p, s, e); } } return null; } }

然后,在`Book`类中使用注解标注:

public class Book { public String name; // 表示反序列化isbn时使用自定义的IsbnDeserializer: @JsonDeserialize(using = IsbnDeserializer.class) public BigInteger isbn; }

`` 类似的,自定义序列化时我们需要自定义一个IsbnSerializer,然后在Book类中标注@JsonSerialize(using = …)`即可。