63

Java XML和JSON:Java SE的文档处理,第1部分

 5 years ago
source link: http://www.apexyun.com/java-xmlhe-json-java-sede-wen-dang-chu-li-di-1bu-fen/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

XML和JSON对我很重要,我很感谢Apress允许我写一本关于它们的书。在这篇 Java Q&A 文章中,我将简要介绍我的新书第二版, Java XML和JSON 。我还将提供两个有用的演示,如果我有足够的空间,我本来希望将其包括在书中。

首先,我将向您展示如何覆盖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_sunmoons XML元素的数字序列化为JSON字符串而不是数字。 readTree() 在没有显式类型定义的情况下,该方法不会推断数据类型。

Jackson对XML树遍历的支持还有其他限制:

  • 杰克逊无法区分对象和数组。由于XML无法区分对象与对象的列表(数组),因此Jackson将重复的元素整理为单个值。
  • 杰克逊不支持 混合内容 (文本内容和元素作为元素的子元素)。相反,它将每个XML元素映射到一个 JsonNode 对象。任何文字都会丢失。

鉴于这些限制, 官方Jackson文档 建议不要将XML解析为 JsonNode 基于树的结构也就不足为奇了。你最好使用数据绑定转换技术。

结论

本文中提供的材料应视为第二版 Java XML和JSON中 第6章和第11章的附录。相比之下,我的下一篇文章将与该书有关,但全新的材料。请关注我即将发布的关于使用JSON-B将Java对象绑定到JSON文档的帖子。

英文原文: https://www.javaworld.com/article/3346229/java-xml-and-json-document-processing-for-java-se-part-1.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK