Serializar y deserializar XML en Java con Jackson

    Introducción

    En un ecosistema cada vez más conectado de sistemas de software, la comunicación entre ellos se ha vuelto aún más primordial. A su vez, se han desarrollado varias tecnologías para empaquetar los datos que se transfieren o comparten entre estos muchos y diferentes sistemas.

    los Lenguaje de marcado extensible, popularmente conocido como XML, es una de las formas de empaquetar los datos que se van a transferir. XML es un lenguaje de formato de documentos que se desarrolló en la década de 1990 ya que HTML no permite la definición de nuevos elementos de texto, es decir, no es extensible. Además de ser extensible, los datos en XML son autodescriptivos, lo que los hace legibles y fáciles de comprender.

    En esta publicación, exploraremos la manipulación XML en Java usando el Biblioteca Jackson.

    Ventajas y desventajas de XML

    XML sigue siendo popular y se utiliza en algunos sistemas, ya que tiene algunas ventajas, pero también han surgido tecnologías más nuevas para cubrir algunas de sus deficiencias.

    Algunas de las ventajas de XML incluyen:

    • XML no está vinculado a una única plataforma o lenguaje de programación y se puede utilizar fácilmente en muchos sistemas diferentes. Esto lo hace adecuado para facilitar la comunicación entre sistemas con diferentes configuraciones de hardware y software.
    • Los datos contenidos en un documento XML se pueden validar mediante una definición de tipo de documento (DTD) o un esquema XML. Este es un conjunto de declaraciones de marcado que definen los componentes básicos de un documento XML.
    • A través de su soporte para Unicode, XML puede contener información escrita en cualquier idioma o formato sin perder ninguna información o contenido en el proceso.
    • Gracias a su compatibilidad con HTML, es fácil leer y mostrar los datos contenidos en un documento XML utilizando HTML.
    • La información almacenada en un documento XML se puede modificar en cualquier momento sin afectar la presentación de los datos a través de otros medios como HTML.

    Algunas de las deficiencias de XML que se han resuelto en nuevas tecnologías incluyen:

    • La sintaxis es bastante redundante y detallada en comparación con otros formatos, como JSON, que es breve y directo al grano.
    • Debido a su sintaxis y naturaleza detallada, los documentos XML suelen ser grandes, lo que puede generar costos adicionales de almacenamiento y transporte.
    • No tiene soporte para matrices.

    Bibliotecas XML

    Manipular XML en Java puede ser un proceso tedioso, por lo que para facilitar el proceso y acelerar el desarrollo, existen varias bibliotecas que podemos usar. Incluyen:

    • Eaxy que es una biblioteca pequeña y simple para construir, manipular, analizar y buscar XML.
    • Arquitectura Java para enlaces XML (JAXB) es un marco para mapear clases de Java a representaciones XML mediante la ordenación de objetos Java en XML y la descomposición de XML en objetos Java. Es parte de la plataforma Java SE.
    • Jackson es una biblioteca para manejar JSON en sistemas Java y ahora tiene soporte para XML desde la versión 2.
    • DOM4J es una biblioteca de memoria eficiente para analizar XML, XPath y XSLT (Lenguaje de hoja de estilo extensible).
    • JDom que es una biblioteca de análisis XML con soporte para XPath y XSLT.

    ¿Qué es Jackson?

    El proyecto Jackson es una colección de herramientas de procesamiento de datos para el lenguaje Java y la plataforma JVM. Admite una amplia gama de formatos de datos como CSV, propiedades de Java, XML y YAML a través de componentes de extensión que admiten el lenguaje específico.

    El componente XML de Jackson está diseñado para leer y escribir datos XML emulando cómo funciona JAXB, aunque no de manera concluyente.

    En este artículo usaremos la biblioteca Jackson para serializar objetos Java en XML y deserializarlos nuevamente en objetos Java.

    Configuración del proyecto

    Primero, configuremos un nuevo proyecto de Maven:

    $ mvn archetype:generate -DgroupId=com.Pharos.sh -DartifactId=xmltutorial -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
    

    Con nuestro proyecto generado, agreguemos la dependencia de Jackson en nuestro pom.xml archivo. Elimine la sección de dependencias existentes y reemplácela con:

    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
      </dependency>
    
      <!-- Jackson dependency for XML manipulation -->
      <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.9.0</version>
      </dependency>
    </dependencies>
    
    <build>
      <plugins>
        <!--
        This plugin configuration will enable Maven to include the project dependencies
        in the produced jar file.
        It also enables us to run the jar file using `java -jar command`
        -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-shade-plugin</artifactId>
          <version>3.2.0</version>
          <executions>
            <execution>
              <phase>package</phase>
              <goals>
                <goal>shade</goal>
              </goals>
              <configuration>
                <transformers>
                  <transformer
                      implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                    <mainClass>com.Pharos.sh.App</mainClass>
                  </transformer>
                </transformers>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
    

    Ahora podemos probar el proyecto que hemos configurado ejecutando los siguientes comandos:

    $ mvn package
    $ java -jar target/java -jar target/xmltutorial-1.0.jar
    

    La salida debe ser Hello World! impreso en nuestra terminal mostrando que nuestro proyecto está listo para el siguiente paso del proyecto.

    Serialización de objetos Java en XML

    Los objetos Java tienen atributos y métodos para manipular estos atributos. En relación con un documento XML, los elementos del documento se pueden asignar a atributos de un objeto Java.

    En el proceso de serialización, los atributos de un objeto se convierten en elementos XML y se almacenan en un documento XML.

    Usaremos un PhoneDetails clase que definirá información sobre un modelo de teléfono en particular, como su nombre, tamaño de pantalla y capacidad de almacenamiento interno. En nuestra clase, estos serán atributos, pero en nuestro documento XML, estos detalles estarán contenidos en etiquetas o elementos.

    Empecemos por definir el PhoneDetails clase que se utilizará para generar nuestros objetos:

    public class PhoneDetails {
        private String name;
        private String displaySize;
        private String memory;
    
        // getters and setters
    }
    

    Con nuestro conjunto de objetos, modifiquemos nuestro App.java y agregue una función para manejar la serialización a XML:

    /**
    * This function writes serializes the Java object into XML and writes it
    * into an XML file.
    */
    public static void serializeToXML() {
        try {
            XmlMapper xmlMapper = new XmlMapper();
    
            // serialize our Object into XML string
            String xmlString = xmlMapper.writeValueAsString(new PhoneDetails("OnePlus", "6.4", "6/64 GB"));
    
            // write to the console
            System.out.println(xmlString);
    
            // write XML string to file
            File xmlOutput = new File("serialized.xml");
            FileWriter fileWriter = new FileWriter(xmlOutput);
            fileWriter.write(xmlString);
            fileWriter.close();
        } catch (JsonProcessingException e) {
            // handle exception
        } catch (IOException e) {
            // handle exception
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Serializing to XML...");
        serializeToXML();
    }
    

    Empaquetemos y ejecutemos nuestro proyecto una vez más:

    $ mvn package
    $ java -jar target/xmltutorial-1.0.jar
    

    La salida en el terminal es:

    <PhoneDetails><name>OnePlus</name><displaySize>6.4</displaySize><memory>6/64 GB</memory></PhoneDetails>
    

    En la carpeta raíz de nuestro proyecto, el serialized.xml Se crea un archivo que contiene esta información. Hemos serializado con éxito nuestro objeto Java en XML y lo hemos escrito en un archivo XML.

    En nuestro serializeToXML() función, creamos una XmlMapper objeto, que es una clase secundaria para el ObjectMapper clase utilizada en la serialización JSON. Esta clase convierte nuestro objeto Java en una salida XML que ahora podemos escribir en un archivo.

    Deserialización de XML

    Jackson también nos permite leer el contenido de un archivo XML y deserializar la cadena XML nuevamente en un objeto Java. En nuestro ejemplo, leeremos un documento XML que contiene detalles sobre un teléfono y usaremos Jackson para extraer estos datos y usarlos para crear objetos Java que contengan la misma información.

    Primero, creemos un documento XML que coincida con nuestra clase para leer. Crear to_deserialize.xml con el siguiente contenido:

    <PhoneDetails>
      <name>iPhone</name>
      <displaySize>6.2</displaySize>
      <memory>3/64 GB</memory>
    </PhoneDetails>
    

    Agreguemos un deserializeFromXML() función para deserializar el archivo XML anterior en un objeto Java:

    public static void deserializeFromXML() {
        try {
            XmlMapper xmlMapper = new XmlMapper();
    
            // read file and put contents into the string
            String readContent = new String(Files.readAllBytes(Paths.get("to_deserialize.xml")));
    
            // deserialize from the XML into a Phone object
            PhoneDetails deserializedData = xmlMapper.readValue(readContent, PhoneDetails.class);
    
            // Print object details
            System.out.println("Deserialized data: ");
            System.out.println("tName: " + deserializedData.getName());
            System.out.println("tMemory: " + deserializedData.getMemory());
            System.out.println("tDisplay Size: " + deserializedData.getDisplaySize());
        } catch (IOException e) {
            // handle the exception
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Deserializing from XML...");
        deserializeFromXML();
    }
    

    Empaquetamos y ejecutamos nuestro proyecto como de costumbre y el resultado es:

    Deserializing from XML...
    
    Deserialized data:
        Name: iPhone
        Memory: 3/64 GB
        Display Size: 6.2
    

    Nuestro archivo XML se ha deserializado con éxito y todos los datos se han extraído con la ayuda de la biblioteca Jackson.

    Anotaciones de Jackson

    Las anotaciones se utilizan para agregar metadatos a nuestro código Java y no tienen ningún efecto directo en la ejecución del código al que están adjuntos. Se utilizan para dar instrucciones al compilador durante el tiempo de compilación y el tiempo de ejecución.

    Jackson usa anotaciones para varias funciones, como definir si estamos mapeando a XML o JSON, definir el orden de atributos y campos en nuestra salida o sus nombres.

    Estas anotaciones generalmente se aplican en nuestros POJOs de Java (Objetos Java antiguos simples). Por ejemplo, podemos anotar nuestro PhoneDetails clase de la siguiente manera:

    public class PhoneDetails {
    
        @JsonProperty("phone_name")
        private String name;
    
        @JsonProperty("display_size")
        private String displaySize;
    
        @JsonProperty("internal_memory")
        private String memory;
    
        // rest of the code remains as is
    }
    

    los @JsonProperty La anotación ayuda a definir el nombre de los campos en nuestro archivo XML. Con esta anotación agregada, las etiquetas en nuestros archivos XML de entrada y salida tendrán que parecerse a las cadenas de la anotación de la siguiente manera:

    <PhoneDetails>
      <phone_name>OnePlus</phone_name>
      <display_size>6.4</display_size>
      <internal_memory>6/64 GB</internal_memory>
    </PhoneDetails>
    

    Otra anotación notable es la @JacksonXmlText que indica que un elemento debe mostrarse como texto sin formato sin etiquetas u otro elemento que lo contenga.

    los @JacksonXmlProperty La anotación se puede utilizar para controlar los detalles del atributo o elemento que se muestra. Dichos detalles pueden incluir el espacio de nombres del elemento. Los espacios de nombres son una forma de asignar elementos a un grupo en particular.

    Un uso principal de los espacios de nombres es evitar conflictos cuando se utilizan etiquetas similares en el documento, ayudan a aislar las etiquetas de un grupo para eliminar cualquier ambigüedad que pueda surgir a medida que los documentos XML escalan.

    El orden de las propiedades también se puede especificar mediante un @JsonPropertyOrder anotación. Por ejemplo, para invertir el orden de los elementos en la salida del documento XML, la anotación se usa de la siguiente manera:

    @JsonPropertyOrder({ "internal_memory", "display_size", "phone_name" })
    public class PhoneDetails {
    
        @JsonProperty("phone_name")
        private String name;
    
        @JsonProperty("display_size")
        private String displaySize;
    
        @JsonProperty("internal_memory")
        private String memory;
    
        ...
    

    La salida de la serialización a XML ahora será:

    <PhoneDetails>
      <internal_memory>6/64 GB</internal_memory>
      <display_size>6.4</display_size>
      <phone_name>OnePlus</phone_name>
    </PhoneDetails>
    

    Si hay campos en los objetos Java que no deseamos serializar, podemos usar el @JsonIgnore anotación y los campos se omitirán durante la serialización y deserialización.

    Las anotaciones de Jackson son útiles para definir y controlar el proceso de serialización y deserialización en varios formatos como XML, JSON y YAML. Algunas anotaciones funcionan para todos los formatos y algunas están vinculadas a un tipo específico de archivo.

    Se pueden encontrar más anotaciones de Jackson y sus usos en este wiki oficial en Github.

    Manipulación de listas y elementos anidados en XML

    Habiendo aprendido sobre las anotaciones, mejoremos nuestro archivo XML para agregar elementos y bucles anidados y modifiquemos nuestro código para serializar y deserializar la siguiente estructura actualizada:

    <PhoneDetails>
      <internal_memory>3/64 GB</internal_memory>
      <display_size>6.2</display_size>
      <phone_name>iPhone X</phone_name>
      <manufacturer>
        <manufacturer_name>Apple</manufacturer_name>
        <country>USA</country>
        <other_phones>
          <phone>iPhone 8</phone>
          <phone>iPhone 7</phone>
          <phone>iPhone 6</phone>
        </other_phones>
      </manufacturer>
    </PhoneDetails>
    

    En esta nueva estructura, hemos introducido un anidado Manufacturer elemento que también incluye una lista de elementos. Con nuestro código actual, no podemos extraer ni crear la nueva sección anidada.

    Para solucionar esto, se requiere una nueva clase para manejar el elemento anidado, y para ese efecto, esto es parte de nuestra nueva Manufacturer clase:

    // define the order of elements
    @JsonPropertyOrder({ "manufacturer_name", "country", "other_phones" })
    public class Manufacturer {
        @JsonProperty("manufacturer_name")
        private String name;
    
        @JsonProperty("country")
        private String country;
    
        // new annotation
        @JacksonXmlElementWrapper(localName="other_phones")
        private List<String> phone;
    
        ...
    

    Es bastante similar a nuestro PhoneDetails class pero ahora hemos introducido una nueva anotación: @JacksonXmlElementWrapper. El propósito de esta anotación es definir si una colección de elementos usa o no un elemento contenedor, y puede usarse para dictar el nombre local y el espacio de nombres de los elementos contenedor.

    En nuestro ejemplo, usamos la anotación para definir el elemento que contiene una lista de elementos y la etiqueta que se usará para ese elemento. Esto se utilizará al serializar y deserializar nuestros archivos XML.

    Este cambio en nuestra estructura XML y la introducción de esta clase requiere que modifiquemos nuestra PhoneDetails clase para reflexionar:

    // existing code remains
    public class PhoneDetails {
        // existing code remains
        @JsonProperty("manufacturer")
        private Manufacturer manufacturer;
    
        // standard getters and setters for the new element
    
        ...
    

    Nuestro PhoneDetails El objeto ahora podrá incluir información sobre el fabricante de un teléfono.

    A continuación, actualizamos nuestro serializeToXML() método:

    public static void serializeToXML() {
        try {
            XmlMapper xmlMapper = new XmlMapper();
    
            // create a list of other phones
            List<String> otherPhones = Arrays.asList("OnePlus 6T", "OnePlus 5T", "OnePlus 5");
    
            // create the manufacturer object
            Manufacturer manufacturer = new Manufacturer("OnePlus", "China", otherPhones);
    
            // serialize our new Object into XML string
            String xmlString = xmlMapper
              .writeValueAsString(new PhoneDetails("OnePlus", "6.4", "6/64 GB", manufacturer));
    
            // write to the console
            System.out.println(xmlString);
    
            // write XML string to file
            File xmlOutput = new File("serialized.xml");
            FileWriter fileWriter = new FileWriter(xmlOutput);
            fileWriter.write(xmlString);
            fileWriter.close();
        } catch (JsonProcessingException e) {
            // handle the exception
        } catch (IOException e) {
            // handle the exception
        }
    }
    

    El resultado de serializar el nuevo PhoneDetails objeto con el Manufacturer la información es:

    Serializing to XML...
    
    <PhoneDetails><internal_memory>6/64 GB</internal_memory><display_size>6.4</display_size><phone_name>OnePlus</phone_name><manufacturer><manufacturer_name>OnePlus</manufacturer_name><country>China</country><other_phones><phones>OnePlus 6T</phones><phones>OnePlus 5T</phones><phones>OnePlus 5</phones></other_phones></manufacturer></PhoneDetails>
    

    ¡Funciona! Nuestro deserializeFromXML() La función, por otro lado, no necesita una actualización importante ya que el PhoneDetails class, cuando se deserializa, también incluirá información del fabricante.

    Agreguemos el siguiente código para imprimir los detalles del fabricante solo para estar seguros:

    // existing code remains
    
    // Print object details
    System.out.println("Deserialized data: ");
    System.out.println("tName: " + deserializedData.getName());
    System.out.println("tMemory: " + deserializedData.getMemory());
    System.out.println("tDisplay Size: " + deserializedData.getDisplaySize());
    System.out.println("tManufacturer Name: " + deserializedData.getManufacturer().getName());
    System.out.println("tManufacturer Country: " + deserializedData.getManufacturer().getCountry());
    System.out.println("tManufacturer Other Phones: " + deserializedData.getManufacturer().getPhone().toString());
    
    // existing code remains
    

    La salida:

    Deserializing from XML...
    
    Deserialized data:
        Name: iPhone X
        Memory: 3/64 GB
        Display Size: 6.2
        Manufacturer Name: Apple
        Manufacturer Country: USA
        Manufacturer Other Phones: [iPhone 8, iPhone 7, iPhone 6]
    

    El proceso de deserialización es perfecto y los nuevos detalles del fabricante se han extraído de nuestro archivo XML actualizado.

    Conclusión

    En esta publicación, hemos aprendido sobre XML y cómo serializar datos en documentos XML, así como deserializar para extraer datos de documentos XML.

    También hemos aprendido acerca de las anotaciones y cómo Jackson las usa en el proceso de serialización y deserialización.

    XML todavía se usa ampliamente en varios sistemas con los que podemos interactuar de vez en cuando, por lo tanto, para interactuar con ellos, necesitaremos serializar y deserializar documentos XML de vez en cuando. También podemos consumir API XML en nuestros proyectos Java mientras exponemos puntos finales REST y usamos Jackson para convertir la entrada XML en salida JSON.

    El código fuente de esta publicación está disponible en Github para referencia.

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *