sangria dink lot

使用 Apache Groovy 驯服 JSON 和 XML

在 Apache Groovy 应用程序中使用内置的 slurper 轻松处理 JSON 和 XML 数据。本指南探讨了…
首页 » 博客 » 使用 Apache Groovy 驯服 JSON 和 XML

Apache Groovy 的 JsonSlurper 和 XmlSlurper 类简化了 JSON 和 XML 解析,提高了您的生产力。

我们将看看 Groovy 为读取和解析 JSON 和 XML 提供了什么。如果您错过了上一个教程,请查看或浏览整个系列

让我们从 JavaScript 对象表示法开始,更广为人知的是 JSON。

Groovy 包 groovy.json 包含一个类 JsonSlurper,它非常容易用于摄取 JSON。

大多数 Linux 安装提供了许多示例 JSON 文件;在我的例子中,我在 /usr/share/iso-codes/json/schema-15924.json 中找到了一个相当复杂的,看起来像这样

{
  "$schema": "https://json-schema.fullstack.org.cn/draft-04/schema#",

  "title": "ISO 15924",
  "description": "Codes for the representation of names of scripts",
  "type": "object",

  "properties": {
	"15924": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "alpha_4": {
            "description": "Four letter alphabetic code of the script",
            "type": "string",
            "pattern": "^[A-Z][a-z]{3}$"
          },
          "name": {
            "description": "Name of the script",
            "type": "string",
            "minLength": 1
          },
          "numeric": {
            "description": "Three digit numeric code of the script, including leading zeros",
            "type": "string",
            "pattern": "^[0-9]{3}$"
          }
        },
        "required": ["alpha_4", "name", "numeric"],
        "additionalProperties": false
      }
    }
  },
  "additionalProperties": false
}

这是一个简单的 Groovy 程序,用于解析该文件并以相当吸引人的形式写回

1   import groovy.json.JsonSlurper
2   def jsonFileName = '/usr/share/iso-codes/json/schema-15924.json'
3   def jsonMap = new JsonSlurper().parse(new File(jsonFileName))
4   walkPrint(jsonMap, '')
5   def walkPrint(json,ldr) {
6       json.each { k, v ->
7      print "$ldr$k: "
8      if (v == null) {
9     print "(null) "
10      } else if (v instanceof String) {
11     print "'$v' "
12      } else if (v instanceof BigDecimal || v instanceof Integer || v instanceof Boolean || v instanceof ArrayList || v instanceof Date) {
13     print "$v "
14      } else {
15     println "["
16     walkPrint(v,ldr + '    ')
17     print "$ldr] "
18      }
19      println "(${v.getClass()})"
20     }
21   }

第一行导入了 JsonSlurper 类。

第二行声明了文件名。

第三行实例化了一个与该文件名关联的 File 对象,然后使用 JsonSlurperparse() 方法来解析它。

第 4-20 行打印解析后的 JSON。

当我们运行上面的程序时,输出看起来像这样

$ groovy Groovy21a.groovy
$schema: 'https://json-schema.fullstack.org.cn/draft-04/schema#' (class java.lang.String)
title: 'ISO 15924' (class java.lang.String)
description: 'Codes for the representation of names of scripts' (class java.lang.String)
type: 'object' (class java.lang.String)
properties: [
  15924: [
    type: 'array' (class java.lang.String)
    items: [
      type: 'object' (class java.lang.String)
      properties: [
        alpha_4: [
          description: 'Four letter alphabetic code of the script' (class java.lang.String)
          type: 'string' (class java.lang.String)
          pattern: '^[A-Z][a-z]{3}$' (class java.lang.String)
        ] (class org.apache.groovy.json.internal.LazyMap)
        name: [
          description: 'Name of the script' (class java.lang.String)
          type: 'string' (class java.lang.String)
          minLength: 1 (class java.lang.Integer)
        ] (class org.apache.groovy.json.internal.LazyMap)
        numeric: [
          description: 'Three digit numeric code of the script, including leading zeros' (class java.lang.String)
          type: 'string' (class java.lang.String)
          pattern: '^[0-9]{3}$' (class java.lang.String)
        ] (class org.apache.groovy.json.internal.LazyMap)
      ] (class org.apache.groovy.json.internal.LazyMap)
      required: [alpha_4, name, numeric] (class java.util.ArrayList)
      additionalProperties: false (class java.lang.Boolean)
    ] (class org.apache.groovy.json.internal.LazyMap)
  ] (class org.apache.groovy.json.internal.LazyMap)
] (class org.apache.groovy.json.internal.LazyMap)
additionalProperties: false (class java.lang.Boolean)

请注意

  • JsonSlurper 将 JSON 转换为 Groovy Map(实际上,正如我们所见,是一个 LazyMap)。回想一下,Map 实例是键值对,这使得在 Groovy 中处理 JSON 对象非常简单。
  • 在方法 walkPrint() 的漂亮打印机代码中,我们看到值可以是简单的(ish),例如 nullStringIntegerBigDecimalDateBoolean;或 ArrayList;或其他,结果证明是 LazyMap。这在 Groovy 关于解析 JSON 的文档 中有进一步的解释,该文档还解释了更多关于 JsonSlurper.parse() 方法的变体的细节,这些变体接受像 String 这样的参数。
  • 最新的 Groovy / Java 环境 中,它支持 Java 的模式匹配 switch 表达式,我们可以使用 switch 表达式来代替带有 instanceofif / else 语句。
  • 我们可以使用 Groovy 的 GPath 表达式语言来获取 JSON 结构的子部分。这种情况适用于程序了解结构的一些信息并想要推理它,例如,如果 JSON 是配置信息。所以在上面的 JSON 文件中,我们可以引用 jsonMap.additionalProperties,它的值将是 false,或者 jsonMap.properties.'15924'.type,它的值将是 ‘array’ 例如(请注意,15924 必须在此处引用,因为它在 Groovy 中不是合法的名称)。

在 Groovy 中读取 XML

处理 XML 非常相似。特别是,有一个包 groovy.xml 包含一个类 XmlSlurper,它与 JsonSlurper 非常相似。

我在我的系统 /usr/share/help-langpack/es/rhythmbox/legal.xml 上找到的文件看起来是一个很好的测试用例。
它看起来像这样

<?xml version="1.0" encoding="utf-8"?>
<legalnotice id="legalnotice">
	<para>Se concede permiso para copiar, distribuir o modificar este documento según las condiciones de la GNU Free Documentation License (GFDL), Versión 1.1 o cualquier versión posterior publicada por la Free Software Foundation sin Secciones invariantes, Textos de portada y Textos de contraportada. Encontrará una copia de la GFDL en este <ulink type="help" url="help:fdl">enlace</ulink> o en el archivo COPYING-DOCS distribuido con este manual.</para>
         <para>Este manual es parte de una colección de manuales de GNOME distribuido bajo la GFDL. Si quiere distribuir este manual por separado de la colección, puede hacerlo añadiendo una copia de la licencia al manual, tal como se describe en la sección 6 de la licencia.</para>

	<para>Muchos de los nombres usados por compañías para distinguir sus productos y servicios son mencionados como marcas comerciales. Donde esos nombres aparezcan en cualquier documentación de GNOME, y los miembros del Proyecto de Documentación de GNOME están al corriente de esas marcas comerciales, entonces los nombres se pondrán en mayúsculas o con la inicial en mayúsculas.</para>

	<para>ESTE DOCUMENTO Y LAS VERSIONES MODIFICADAS DEL MISMO SE PROPORCIONAN SEGÚN LAS CONDICIONES ESTABLECIDAS EN LA LICENCIA DE DOCUMENTACIÓN LIBRE DE GNU (GFDL) Y TENIENDO EN CUENTA QUE: <orderedlist>
		<listitem>
		  <para>EL DOCUMENTO SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, NI EXPLÍCITA NI IMPLÍCITA INCLUYENDO, SIN LIMITACIÓN, GARANTÍA DE QUE EL DOCUMENTO O VERSIÓN MODIFICADA DE ÉSTE CAREZCA DE DEFECTOS COMERCIALES, SEA ADECUADO A UN FIN CONCRETO O INCUMPLA ALGUNA NORMATIVA. TODO EL RIESGO RELATIVO A LA CALIDAD, PRECISIÓN Y UTILIDAD DEL DOCUMENTO O SU VERSIÓN MODIFICADA RECAE EN USTED. SI CUALQUIER DOCUMENTO O VERSIÓN MODIFICADA DE AQUÉL RESULTARA DEFECTUOSO EN CUALQUIER ASPECTO, USTED (Y NO EL REDACTOR INICIAL, AUTOR O CONTRIBUYENTE) ASUMIRÁ LOS COSTES DE TODA REPARACIÓN, MANTENIMIENTO O CORRECCIÓN NECESARIOS. ESTA RENUNCIA DE GARANTÍA ES UNA PARTE ESENCIAL DE ESTA LICENCIA. NO SE AUTORIZA EL USO DE NINGÚN DOCUMENTO NI VERSIÓN MODIFICADA DE ÉSTE POR EL PRESENTE, SALVO DENTRO DEL CUMPLIMIENTO DE LA RENUNCIA;Y</para>
		</listitem>
		<listitem>
		  <para>BAJO NINGUNA CIRCUNSTANCIA NI BAJO NINGUNA TEORÍA LEGAL, SEA POR ERROR (INCLUYENDO NEGLIGENCIA), CONTRATO O DE ALGÚN OTRO MODO, EL AUTOR, EL ESCRITOR INICIAL, CUALQUIER CONTRIBUIDOR, O CUALQUIER DISTRIBUIDOR DEL DOCUMENTO O VERSIÓN MODIFICADA DEL DOCUMENTO, O CUALQUIER PROPORCIONADOR DE CUALQUIERA DE ESAS PARTES, SERÁ RESPONSABLE ANTE NINGUNA PERSONA POR NINGÚN DAÑO DIRECTO, INDIRECTO, ESPECIAL, INCIDENTAL O DERIVADO DE NINGÚN TIPO, INCLUYENDO, SIN LIMITACIÓN DAÑOS POR PÉRDIDA DE MERCANCÍAS, PARO TÉCNICO, FALLO INFORMÁTICO O MAL FUNCIONAMIENTO O CUALQUIER OTRO POSIBLE DAÑO O PÉRDIDAS DERIVADAS O RELACIONADAS CON EL USO DEL DOCUMENTO O SUS VERSIONES MODIFICADAS, AUNQUE DICHA PARTE HAYA SIDO INFORMADA DE LA POSIBILIDAD DE QUE SE PRODUJESEN DICHOS DAÑOS.</para>
		</listitem>
	  </orderedlist></para>
  </legalnotice>

这是一个简单的 Groovy 程序,用于解析和遍历该 XML 文件

1   import groovy.xml.XmlSlurper
2   def xmlFileName = '/usr/share/help-langpack/es/rhythmbox/legal.xml'
3   def xmlMap = new XmlSlurper().parse(new File(xmlFileName))
4   walkPrint(xmlMap, '')
5   def walkPrint(xml,ldr) {
6       println "${ldr}name ${xml.name()} attributes ${xml.attributes()} localText ${xml.localText()}"
7       xml.'*'.each { c ->
8      walkPrint(c, ldr + '    ')
9       }
10   }

第一行导入了 XmlSlurper 类。

第二行声明了文件名。

第三行实例化了一个与该文件名关联的 File 对象,然后使用 XmlSlurperparse() 方法来解析它。

第 4 到 9 行打印解析后的 XML。请注意,解析返回的结构与 JSON 情况不同。特别是,该结构是一个 groovy.xml.slurpersupport.NodeChild 元素的树,该类扩展了 groovy.xml.slurpersupport.GPathResult。这些元素在 Groovy 编程手册NodeChild 类的参考文档 中有更详细的解释。我们使用三种方法来打印树的详细信息

  • name() 产生节点的名称,例如我们例子中的 “para”
  • attributes() 产生节点的属性映射(如果有)
  • localText() 产生与节点直接关联的任何文本字符串的列表

我们还使用 Groovy 简写星号 (*) 来使用 GPath 语言语法引用当前节点的 children()

当我们运行它时,我们看到

$ groovy Groovy21b.groovy
name legalnotice attributes [id:legalnotice] localText [] name para attributes [:] localText [Se concede permiso para copiar, distribuir o modificar este documento según las condiciones de la GNU Free Documentation License (GFDL), Versión 1.1 o cualquier versión posterior publicada por la Free Software Foundation sin Secciones invariantes, Textos de portada y Textos de contraportada. Encontrará una copia de la GFDL en este , o en el archivo COPYING-DOCS distribuido con este manual.] name ulink attributes [type:help, url:help:fdl] localText [enlace] name para attributes [:] localText [Este manual es parte de una colección de manuales de GNOME distribuido bajo la GFDL. Si quiere distribuir este manual por separado de la colección, puede hacerlo añadiendo una copia de la licencia al manual, tal como se describe en la sección 6 de la licencia.] name para attributes [:] localText [Muchos de los nombres usados por compañías para distinguir sus productos y servicios son mencionados como marcas comerciales. Donde esos nombres aparezcan en cualquier documentación de GNOME, y los miembros del Proyecto de Documentación de GNOME están al corriente de esas marcas comerciales, entonces los nombres se pondrán en mayúsculas o con la inicial en mayúsculas.] name para attributes [:] localText [ESTE DOCUMENTO Y LAS VERSIONES MODIFICADAS DEL MISMO SE PROPORCIONAN SEGÚN LAS CONDICIONES ESTABLECIDAS EN LA LICENCIA DE DOCUMENTACIÓN LIBRE DE GNU (GFDL) Y TENIENDO EN CUENTA QUE: ] name orderedlist attributes [:] localText [] name listitem attributes [:] localText [] name para attributes [:] localText [EL DOCUMENTO SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, NI EXPLÍCITA NI IMPLÍCITA INCLUYENDO, SIN LIMITACIÓN, GARANTÍA DE QUE EL DOCUMENTO O VERSIÓN MODIFICADA DE ÉSTE CAREZCA DE DEFECTOS COMERCIALES, SEA ADECUADO A UN FIN CONCRETO O INCUMPLA ALGUNA NORMATIVA. TODO EL RIESGO RELATIVO A LA CALIDAD, PRECISIÓN Y UTILIDAD DEL DOCUMENTO O SU VERSIÓN MODIFICADA RECAE EN USTED. SI CUALQUIER DOCUMENTO O VERSIÓN MODIFICADA DE AQUÉL RESULTARA DEFECTUOSO EN CUALQUIER ASPECTO, USTED (Y NO EL REDACTOR INICIAL, AUTOR O CONTRIBUYENTE) ASUMIRÁ LOS COSTES DE TODA REPARACIÓN, MANTENIMIENTO O CORRECCIÓN NECESARIOS. ESTA RENUNCIA DE GARANTÍA ES UNA PARTE ESENCIAL DE ESTA LICENCIA. NO SE AUTORIZA EL USO DE NINGÚN DOCUMENTO NI VERSIÓN MODIFICADA DE ÉSTE POR EL PRESENTE, SALVO DENTRO DEL CUMPLIMIENTO DE LA RENUNCIA;Y] name listitem attributes [:] localText [] name para attributes [:] localText [BAJO NINGUNA CIRCUNSTANCIA NI BAJO NINGUNA TEORÍA LEGAL, SEA POR ERROR (INCLUYENDO NEGLIGENCIA), CONTRATO O DE ALGÚN OTRO MODO, EL AUTOR, EL ESCRITOR INICIAL, CUALQUIER CONTRIBUIDOR, O CUALQUIER DISTRIBUIDOR DEL DOCUMENTO O VERSIÓN MODIFICADA DEL DOCUMENTO, O CUALQUIER PROPORCIONADOR DE CUALQUIERA DE ESAS PARTES, SERÁ RESPONSABLE ANTE NINGUNA PERSONA POR NINGÚN DAÑO DIRECTO, INDIRECTO, ESPECIAL, INCIDENTAL O DERIVADO DE NINGÚN TIPO, INCLUYENDO, SIN LIMITACIÓN DAÑOS POR PÉRDIDA DE MERCANCÍAS, PARO TÉCNICO, FALLO INFORMÁTICO O MAL FUNCIONAMIENTO O CUALQUIER OTRO POSIBLE DAÑO O PÉRDIDAS DERIVADAS O RELACIONADAS CON EL USO DEL DOCUMENTO O SUS VERSIONES MODIFICADAS, AUNQUE DICHA PARTE HAYA SIDO INFORMADA DE LA POSIBILIDAD DE QUE SE PRODUJESEN DICHOS DAÑOS.] 
$

还有其他处理 XML 的选项,这些选项在上面的参考资料中有所记录。我们在 JSON 中看到的相同情况,即使用 GPath 表示法访问已知的子节点,也适用于 XML 文件。然而,XML 的典型用途(如上所示)是具有许多相似的元素(参见上面的 <para>),因此在 XML 中,元素的索引发挥了作用。也就是说,上面示例中的第一个 <para> 元素(元素编号 0)具有 localText() “Se concede permiso para copiar…”,我们将使用 xmlMap.para[0].localText() 来访问它;第二个等等也是如此。方法 xmlMap.para.size() 告诉我们结构中存在多少个名为 ‘para’ 的元素。

就这样!

结论

一行 Groovy 代码:这就是将 JSON 或 XML 解析为可用结构所需的全部。Groovy slurper:强大而简洁。

作者

如果您喜欢这篇文章,您可能也会喜欢这些