XML和JSON對我很重要,我很感謝Apress允許我寫一本關於它們的書。在這篇Java Q&A文章中,我將簡要介紹我的新書第二版,
。我還將提供兩個有用的演示,如果我有足夠的空間,我本來希望將其包括在書中。首先,我將向您展示如何覆蓋Xalan,它是Java 11的標準XSLT實現,具有XSLT 2.0+和XPath 2.0 +相容的替代方案,在本例中為SAXON。使用SAXON for XSLT / XPath可以更輕鬆地訪問分組等功能,我還將演示。接下來,我將向您展示使用Jackson將XML轉換為JSON的兩種方法:第一種技術是資料繫結,第二種是樹遍歷。
為什麼選擇XML和JSON?
在XML到來之前,我編寫了軟體來匯入以未記錄的二進位制格式儲存的資料。我使用偵錯程式來識別資料欄位型別,檔案偏移量和長度。當XML出現,然後是JSON時,這項技術大大簡化了我的生活。
第一版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進行了更新。
在寫完這本書後,我分別寫了兩個部分,分別介紹了SAXON和Jackson的有用功能。我將在這篇文章中介紹這些部分。首先,我將花一點時間介紹這本書及其內容。
Java XML和JSON,第二版
理想情況下,在研究本文中的其他內容之前,您應該閱讀第二版
第二版Java XML和JSON分為三個部分,包括12章和附錄:
- 第1部分:探索XML
- 第1章:XML簡介第
- 2章:使用SAX解析XML文件
- 第3章:使用DOM解析和建立XML文件
- 第4章:使用StAX解析和建立XML文件
- 第5章:使用XPath選擇節點
- 第6章:使用XSLT轉換XML文件
- 第2部分:探索JSON
- 第7章:JSON簡介
- 第8章:使用mJson解析和建立JSON物件
- 第9章:使用Gson解析和建立JSON物件
- 第10章:使用JsonPath提取JSON值
- 第11章:使用Jackson處理JSON第12章:使用JSON-P處理JSON
- 第3部分:附錄附錄A:練習答案
第1部分側重於XML。第1章定義了關鍵術語,介紹了XML語言特性(XML宣告,元素和屬性,字元引用和CDATA部分,名稱空間,註釋和處理指令),並介紹了XML文件驗證(通過文件型別定義和模式)。其餘五章探討了Java SE的SAX,DOM,StAX,XPath和XSLT API。
第1部分側重於XML。第1章定義了關鍵術語,介紹了XML語言特性(XML宣告,元素和屬性,字元引用和CDATA部分,名稱空間,註釋和處理指令),並介紹了XML文件驗證(通過文件型別定義和模式)。其餘五章探討了Java SE的SAX,DOM,StAX,XPath和XSLT 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上下文中非正式使用。
每一章都以一系列練習結束,包括程式設計練習,旨在加強讀者對材料的理解。答案在書的附錄中公佈。
新版本在某些重要方面與其前身不同:
- 第2章介紹了獲取XML閱讀器的正確方法。上一版的方法已被棄用。
- 第3章還介紹了DOM的載入和儲存,範圍和遍歷API。
- 第6章介紹瞭如何使用SAXON超越XSLT / XPath 1.0。
- 第11章是探索傑克遜的一個新的(冗長的)章節。
- 第12章是探索JSON-P的新(冗長)章節。
此版本還糾正了上一版內容中的小錯誤,更新了各種數字,並新增了許多新練習。
雖然我在第二版中沒有空間,但
第6章附錄:使用XSLT轉換XML文件
使用SAXON超越XSLT / XPath 1.0
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的選擇。
XSLT / XPath功能:一個演示
第6章介紹了兩個XSLTDemo
應用程式,第三個應用程式可以在本書的程式碼存檔中找到。下面的清單1提供了第四個XSLTDemo
演示應用程式,它突出了XSLT / XPath功能。
清單1. XSLTDemo.java
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 2.0示例:對節點進行分組
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
按書名對作者姓名進行分組的檔案的內容。
清單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轉換,可以將此文件轉換為根據作者名稱對書名進行分組的文件。
清單3. books.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}" />
構造指定的輸出元素進行排序
Transformation
現在讓我們嘗試轉型。執行以下命令:
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>
複製程式碼
第11章附錄:與Jackon一起處理JSON
使用Jackson將XML轉換為JSON
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轉換為JSON
清單4. planet.xml
<?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
內容。
清單5. Planet.java
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所示的應用程式中摘錄了這些程式碼片段。
清單6. XML2JSON.java(版本1)
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的另一種方法是首先將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所示的應用程式中摘錄了這些程式碼片段。
清單7. XML2JSON.java(版本2)
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樹遍歷的支援還有其他限制:
- Jackson無法區分物件和陣列。由於XML無法區分物件與物件的列表(陣列),因此Jackson將重複的元素整理為單個值。
- 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
(未經同意,請勿轉載)