Java XML和JSON:Java SE的文檔處理,第1部分

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和JSON?

在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

Java XML和JSON,第二版

理想狀況下,在研究本文中的其餘內容以前,您應該閱讀第二版apache

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。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上下文中非正式使用。數組

每一章都以一系列練習結束,包括編程練習,旨在增強讀者對材料的理解。答案在書的附錄中公佈。

新版本在某些重要方面與其前身不一樣:

  • 第2章介紹了獲取XML閱讀器的正確方法。上一版的方法已被棄用。
  • 第3章還介紹了DOM的加載和保存,範圍和遍歷API。
  • 第6章介紹瞭如何使用SAXON超越XSLT / XPath 1.0。
  • 第11章是探索傑克遜的一個新的(冗長的)章節。
  • 第12章是探索JSON-P的新(冗長)章節。

此版本還糾正了上一版內容中的小錯誤,更新了各類數字,並添加了許多新練習。

雖然我在第二版中沒有空間,但

Java XML和JSON
的將來版本可能涵蓋 YAML

第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

數據綁定
容許您將序列化數據映射到Java對象。例如,假設您有一個描述單個行星的小型XML文檔。清單4給出了這個文檔。

清單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.jarstax2-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());
複製代碼

ObjectMapperJsonNode 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_sunmoonsXML元素的數字序列化爲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

(未經贊成,請勿轉載)

相關文章
相關標籤/搜索