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

銀河1號發表於2019-04-02

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
。即使您尚未閱讀本書,您也應該知道它涵蓋的內容,因為該資訊會將其他部分放在上下文中。

第二版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的新(冗長)章節。

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

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

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

(未經同意,請勿轉載)


相關文章