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
(未經贊成,請勿轉載)