XML和JSON对我很重要,我很感谢Apress容许我写一本关于它们的书。在这篇Java Q&A文章中,我将简要介绍个人新书第二版,html
。我还将提供两个有用的演示,若是我有足够的空间,我原本但愿将其包括在书中。首先,我将向您展现如何覆盖Xalan,它是Java 11的标准XSLT实现,具备XSLT 2.0+和XPath 2.0 +兼容的替代方案,在本例中为SAXON。使用SAXON for XSLT / XPath能够更轻松地访问分组等功能,我还将演示。接下来,我将向您展现使用Jackson将XML转换为JSON的两种方法:第一种技术是数据绑定,第二种是树遍历。java
在XML到来以前,我编写了软件来导入以未记录的二进制格式存储的数据。我使用调试器来识别数据字段类型,文件偏移量和长度。当XML出现,而后是JSON时,这项技术大大简化了个人生活。node
初版Java XML和JSON(2016年6月)介绍了XML和JSON,探讨了Java SE本身的面向XML的API,并探讨了面向Java SE的外部面向JSON的API。最近由Apress发布的第二版提供了新内容,而且(但愿)回答了有关XML,JSON,Java SE的XML API和各类JSON API(包括JSON-P)的更多问题。它也针对Java SE 11进行了更新。git
在写完这本书后,我分别写了两个部分,分别介绍了SAXON和Jackson的有用功能。我将在这篇文章中介绍这些部分。首先,我将花一点时间介绍这本书及其内容。github
理想状况下,在研究本文中的其余内容以前,您应该阅读第二版apache
第二版Java XML和JSON分为三个部分,包括12章和附录:编程
第1部分侧重于XML。第1章定义了关键术语,介绍了XML语言特性(XML声明,元素和属性,字符引用和CDATA部分,命名空间,注释和处理指令),并介绍了XML文档验证(经过文档类型定义和模式)。其他五章探讨了Java SE的SAX,DOM,StAX,XPath和XSLT API。json
第1部分侧重于XML。第1章定义了关键术语,介绍了XML语言特性(XML声明,元素和属性,字符引用和CDATA部分,命名空间,注释和处理指令),并介绍了XML文档验证(经过文档类型定义和模式)。其他五章探讨了Java SE的SAX,DOM,StAX,XPath和XSLT API。api
第2部分重点介绍JSON。第7章定义了关键术语,浏览JSON语法,在JavaScript上下文中演示JSON(由于Java SE还没有正式支持JSON),并展现了如何验证JSON对象(经过JSON Schema Validator在线工具)。其他五章探讨第三方mJSon,Gson,JsonPath和Jackson API; 和Oracle面向Java EE的JSON-P API,它也能够在Java SE上下文中非正式使用。数组
每一章都以一系列练习结束,包括编程练习,旨在增强读者对材料的理解。答案在书的附录中公布。
新版本在某些重要方面与其前身不一样:
此版本还纠正了上一版内容中的小错误,更新了各类数字,并添加了许多新练习。
虽然我在第二版中没有空间,但
Java 11的XSLT实现基于Apache Xalan Project,它支持XSLT 1.0和XPath 1.0,但仅限于这些早期版本。要访问之后的XSLT 2.0+和XPath 2.0+功能,您须要使用SAXON等替代方法覆盖Xalan实现。
Java XML和JSON,第6章介绍了如何使用SAXON覆盖Xalan,而后验证是否正在使用SAXON。在演示中,我建议在应用程序的main()
方法开头插入如下行,以便使用SAXON:
System.setProperty("javax.xml.transform.TransformerFactory",
"net.sf.saxon.TransformerFactoryImpl");
复制代码
您实际上不须要此方法调用,由于SAXON的TransformerFactory
实如今JAR文件中做为服务提供,当经过类路径访问JAR文件时,该服务会自动加载。可是,若是TransformerFactory
类路径上有多个实现JAR文件,而且Java运行时选择非SAXON服务做为转换器实现,则可能存在问题。包括上述方法调用将覆盖SAXON的选择。
第6章介绍了两个XSLTDemo
应用程序,第三个应用程序能够在本书的代码存档中找到。下面的清单1提供了第四个XSLTDemo
演示应用程序,它突出了XSLT / XPath功能。
import java.io.FileReader;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import static java.lang.System.*;
public class XSLTDemo
{
public static void main(String[] args)
{
if (args.length != 2)
{
err.println("usage: java XSLTDemo xmlfile xslfile");
return;
}
try
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(args[0]);
TransformerFactory tf = TransformerFactory.newInstance();
out.printf("TransformerFactory: %s%n", tf);
FileReader fr = new FileReader(args[1]);
StreamSource ssStyleSheet = new StreamSource(fr);
Transformer t = tf.newTransformer(ssStyleSheet);
Source source = new DOMSource(doc);
Result result = new StreamResult(out);
t.transform(source, result);
}
catch (IOException ioe)
{
err.printf("IOE: %s%n", ioe.toString());
}
catch (FactoryConfigurationError fce)
{
err.printf("FCE: %s%n", fce.toString());
}
catch (ParserConfigurationException pce)
{
err.printf("PCE: %s%n", pce.toString());
}
catch (SAXException saxe)
{
err.printf("SAXE: %s%n", saxe.toString());
}
catch (TransformerConfigurationException tce)
{
err.printf("TCE: %s%n", tce.toString());
}
catch (TransformerException te)
{
err.printf("TE: %s%n", te.toString());
}
catch (TransformerFactoryConfigurationError tfce)
{
err.printf("TFCE: %s%n", tfce.toString());
}
}
}复制代码
清单1中的代码相似于第6章的清单6-2,可是存在一些差别。首先,main()
必须使用两个命令行参数调用清单1的方法:第一个参数命名XML文件; 第二个参数命名XSL文件。
第二个区别是我没有在变压器上设置任何输出属性。具体来讲,我没有指定输出方法或是否使用缩进。这些任务能够在XSL文件中完成。
编译清单1以下:
javac XSLTDemo.java
复制代码
XSLT 1.0不提供对分组节点的内置支持。例如,您可能但愿转换如下XML文档,该文档列出了做者的书籍:
<book title="Book 1">
<author name="Author 1" />
<author name="Author 2" />
</book>
<book title="Book 2">
<author name="Author 1" />
</book>
<book title="Book 3">
<author name="Author 2" />
<author name="Author 3" />
</book>复制代码
进入如下XML,其中列出了做者的书籍:
<author name="Author 1">
<book title="Book 1" />
<book title="Book 2" />
</author>
<author name="Author 2">
<book title="Book 1" />
<book title="Book 3" />
</author>
<author name="Author 3">
<book title="Book 3" />
</author>
复制代码
虽然这种转换在XSLT 1.0中是可行的,但它很尴尬。xsl:for-each-group
相比之下,XSLT 2.0的元素容许您获取一组节点,按某些标准对其进行分组,并处理每一个建立的组。
让咱们从要处理的XML文档开始探索此功能。清单2显示了books.xml
按书名对做者姓名进行分组的文件的内容。
<?xml version="1.0"?>
<books>
<book title="Securing Office 365: Masterminding MDM and Compliance in the Cloud">
<author name="Matthew Katzer"/>
<publisher name="Apress" isbn="978-1484242292" pubyear="2019"/>
</book>
<book title="Office 2019 For Dummies">
<author name="Wallace Wang"/>
<publisher name="For Dummies" isbn="978-1119513988" pubyear="2018"/>
</book>
<book title="Office 365: Migrating and Managing Your Business in the Cloud">
<author name="Matthew Katzer"/>
<author name="Don Crawford"/>
<publisher name="Apress" isbn="978-1430265269" pubyear="2014"/>
</book>
</books>复制代码
清单3显示了一个books.xsl
文件的内容,该文件提供了XSL转换,能够将此文档转换为根据做者名称对书名进行分组的文档。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/books">
<html>
<head>
</head>
<body>
<xsl:for-each-group select="book/author" group-by="@name">
<xsl:sort select="@name"/>
<author name="{@name}">
<xsl:for-each select="current-group()">
<xsl:sort select="../@title"/>
<book title="{../@title}" />
</xsl:for-each>
</author>
</xsl:for-each-group>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
复制代码
该xsl:output
元素表示须要缩进的HTML输出。的xsl:template-match
元件的单相匹配books
根元素。该xsl:for-each-group
元素选择一系列节点并将它们组织成组。该select
属性是一个XPath表达式,用于标识要分组的元素。在这里,它被告知选择author
属于book
元素的全部元素。该group-by
属性将具备相同值的全部元素组合在一块儿,分组键刚好是元素的@name
属性author
。实质上,您最终获得如下组:
Group 1
Matthew Katzer
Matthew Katzer
Group 2
Wallace Wang
Group 3
Don Crawford
复制代码
这些组不是做者姓名的字母顺序,所以author
将输出元素,这Matthew Katzer
是第一个Don Crawford
也是最后一个。该xsl:sort select="@name"
元素确保author
元素按排序顺序输出。
该<author name="{@name}">
构造输出一个<author>
标签,其name
属性仅分配给组中的第一个做者名称。
继续,xsl:for-each select="current-group()"
迭代当前for-each-group
迭代组中的做者姓名。该xsl:sort select="../@title"
构造将根据书名对book
经过后续<book title="{../@title}" />
构造指定的输出元素进行排序
如今让咱们尝试转型。执行如下命令:
java XSLTDemo books.xml books.xsl
复制代码
遗憾的是,此转换失败:您应该观察将Apache Xalan标识为变换器工厂的输出以及声明xsl:for-each-group
不支持的错误消息。
让咱们再试一次。假设saxon9he.jar
而且XSLTDemo.class
位于当前目录中,请执行如下命令:
java -cp saxon9he.jar;. XSLTDemo books.xml books.xsl复制代码
这一次,您应该观察如下排序和正确分组的输出:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<author name="Don Crawford">
<book title="Office 365: Migrating and Managing Your Business in the Cloud"></book>
</author>
<author name="Matthew Katzer">
<book title="Office 365: Migrating and Managing Your Business in the Cloud"></book>
<book title="Securing Office 365: Masterminding MDM and Compliance in the Cloud"></book>
</author>
<author name="Wallace Wang">
<book title="Office 2019 For Dummies"></book>
</author>
</body>
</html>
复制代码
Java XML和JSON,第11章介绍了Jackson,它提供了用于解析和建立JSON对象的API。也可使用Jackson将XML文档转换为JSON文档。
在本节中,我将向您展现将XML转换为JSON的两种方法,首先是数据绑定,而后是树遍历。我假设你已经读过第11章并熟悉杰克逊。为了遵循这些演示,您应该从Maven存储库下载如下JAR文件:
jackson-annotations-2.9.7.jar
jackson-core-2.9.7.jar
jackson-databind-2.9.7.jar
您还须要一些额外的JAR文件; 大多数转换技术都很常见。我将尽快提供有关获取这些JAR文件的信息。
<?xml version="1.0" encoding="UTF-8"?>
<planet>
<name>Earth</name>
<planet_from_sun>3</planet_from_sun>
<moons>9</moons>
</planet>
复制代码
清单5展现了一个等效的Java Planet
类,其对象映射到了planet.xml
内容。
public class Planet
{
public String name;
public Integer planet_from_sun;
public Integer moons;
}
复制代码
转换过程要求您首先将XML解析为Planet
对象。您能够经过使用com.fasterxml.jackson.dataformat.xml.XmlMapper
该类来完成此任务,以下所示:
XmlMapper xmlMapper = new XmlMapper();
XMLInputFactory xmlif = XMLInputFactory.newFactory();
FileReader fr = new FileReader("planet.xml");
XMLStreamReader xmlsr = xmlif.createXMLStreamReader(fr);
Planet planet = xmlMapper.readValue(xmlsr, Planet.class);
复制代码
XmlMapper
是一个com.fasterxml.jackson.databind.ObjectMapper
读取和写入XML 的自定义。它提供了几种readValue()
从特定于XML的输入源读取单个XML值的方法; 例如:
<T> T readValue(XMLStreamReader r, Class<T> valueType)
复制代码
每一个readValue()
方法都须要一个javax.xml.stream.XMLStreamReader
对象做为其第一个参数。该对象本质上是一个基于StAX的基于流的解析器,用于之前向方式有效地解析文本。
第二个参数是java.lang.Class
正在实例化的目标类型的对象,填充了XML数据,随后从该方法返回其实例。
这段代码片断的底线是清单4的内容被读入一个返回给它的调用者的Planet
对象readValue()
。
一旦建立了对象,就能够经过使用ObjectMapper
它的String writeValueAsString(Object value)
方法将其写成JSON :
ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(planet);
复制代码
我从一个XML2JSON
完整源代码如清单6所示的应用程序中摘录了这些代码片断。
import java.io.FileReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import static java.lang.System.*;
public class XML2JSON
{
public static void main(String[] args) throws Exception
{
XmlMapper xmlMapper = new XmlMapper();
XMLInputFactory xmlif = XMLInputFactory.newFactory();
FileReader fr = new FileReader("planet.xml");
XMLStreamReader xmlsr = xmlif.createXMLStreamReader(fr);
Planet planet = xmlMapper.readValue(xmlsr, Planet.class);
ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(planet);
out.println(json);
}
}
复制代码
以前,你能够编译清单5和6,你须要下载杰克逊DATAFORMAT XML,它实现XMLMapper
。我下载了2.9.7版,与其余三个Jackson软件包的版本相匹配。
假设您已成功下载jackson-dataformat-xml-2.9.7.jar
,请执行如下命令(分为两行以便于阅读)以编译源代码:
javac -cp jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;jackson-dataformat-xml-2.9.7.jar;.
XML2JSON.java
复制代码
在运行生成的应用程序以前,您须要下载Jackson Module:JAXB Annotations,并下载StAX 2 API。我下载了JAXB Annotations版本2.9.7和StAX 2 API版本3.1.3。
假设您已成功下载jackson-module-jaxb-annotations-2.9.7.jar
并stax2-api-3.1.3.jar
执行如下命令(分为三行以便于阅读)以运行应用程序:
java -cp jackson-annotations-2.9.7.jar;jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;
jackson-dataformat-xml-2.9.7.jar;jackson-module-jaxb-annotations-2.9.7.jar; stax2-api-3.1.3.jar;.
XML2JSON
复制代码
若是一切顺利,您应该观察如下输出:
{"name":"Earth","planet_from_sun":3,"moons":9}
复制代码
从XML转换为JSON的另外一种方法是首先将XML解析为JSON节点树,而后将此树写入JSON文档。您能够经过调用其中一个XMLMapper
继承的readTree()
方法来完成第一个任务:
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(xml.getBytes());
复制代码
ObjectMapper
该JsonNode readTree(byte[] content)
方法将JSON内容反序列化为jackson.databind.JsonNode
对象树,并返回JsonNode
该树的根对象。在XmlMapper
上下文中,此方法将XML内容反序列化为树。在任何一种状况下,JSON或XML内容都做为字节数组传递给此方法。
第二个任务 - 将对象树转换为JSON - 以与我以前显示的方式相似的方式完成。这一次,它JsonNode
是传递给的根对象writeValueAsString()
:
ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(node);
复制代码
我从一个XML2JSON
完整源代码如清单7所示的应用程序中摘录了这些代码片断。
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import static java.lang.System.*;
public class XML2JSON
{
public static void main(String[] args) throws Exception
{
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<planet>\n" +
" <name>Earth</name>\n" +
" <planet_from_sun>3</planet_from_sun>\n" +
" <moons>1</moons>\n" +
"</planet>\n";
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(xml.getBytes());
ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(node);
out.println(json);
}
}
复制代码
执行如下命令(分为两行以便于阅读)以编译清单7:
javac -cp jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;jackson-dataformat-xml-2.9.7.jar
XML2JSON.java
复制代码
在运行生成的应用程序以前,您须要下载Woodstox,它是一个实现StAX,SAX2和StAX2的高性能XML处理器。我下载了Woodstox 5.2.0。而后执行如下命令(分布在三行以便于阅读)以运行应用程序:
java -cp jackson-annotations-2.9.7.jar;jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;
jackson-dataformat-xml-2.9.7.jar;stax2-api-3.1.3.jar;woodstox-core-5.2.0.jar;.
XML2JSON
复制代码
若是一切顺利,您应该观察如下输出:
{"name":"Earth","planet_from_sun":"3","moons":"1"}
复制代码
请注意,分配给XML元素planet_from_sun
和moons
XML元素的数字序列化为JSON字符串而不是数字。readTree()
在没有显式类型定义的状况下,该方法不会推断数据类型。
Jackson对XML树遍历的支持还有其余限制:
JsonNode
对象。任何文字都会丢失。鉴于这些限制,官方Jackson文档建议不要将XML解析为JsonNode
基于树的结构也就不足为奇了。你最好使用数据绑定转换技术。
本文中提供的材料应视为第二版Java XML和JSON中第6章和第11章的附录。相比之下,个人下一篇文章将与该书有关,但全新的材料。请关注我即将发布的关于使用JSON-B将Java对象绑定到JSON文档的帖子。
英文原文:www.javaworld.com/article/334…
公众号:银河系1号
联系邮箱:public@space-explore.com
(未经赞成,请勿转载)