Skip to content

Commit 7adf346

Browse files
committed
Añade información sobre writer (Jackson)
1 parent 897b661 commit 7adf346

File tree

2 files changed

+192
-16
lines changed

2 files changed

+192
-16
lines changed

source/02.formatos/02.json/02.jackson.rst

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,21 @@ Ilustrémoslo:
170170
de estas clases respectivamente en vez de las clases más concretas
171171
|JsonMapper.Builder| y |JsonMapper|.
172172

173+
.. tip:: El ejemplo ilustra cómo puede habilitarse con ``.enable()`` una
174+
característica de serialización que por defecto esté desactiva. y obviamente
175+
puede hacerse al contrario con ``.disable()``. También existe ``.configure()``
176+
que añade un segundo argumento *booleano* para expresar si se quiere
177+
habilitada o deshabilitada la característica:
178+
179+
.. code-block:: java
180+
181+
JsonFactory factory = JsonFactory.builder()
182+
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
183+
.build();
184+
185+
.. seealso:: El control de la serialización puede hacerse de un modo más exhaustivo
186+
utilizando el :ref:`concepto de writer <jackson-writer>`. Lo introduciremos más adelante.
187+
173188
Estás tres etapas pueden simplificarse si necesitamos menos configuración. Ya hemos
174189
visto que pueden reducirse a una, si no necesitamos configuración adicional en
175190
absoluto:
@@ -473,6 +488,117 @@ anotación individual en cada uno de los atributos y hacer lo siguiente:
473488
y deserializadora (|LocalDateDeserializer|) ya están definidas y nos
474489
limitamos a usarlas en vez de definirlas nosotros.
475490

491+
.. _jackson-writer:
492+
493+
Control de la serialización
494+
===========================
495+
496+
.. rubric:: Características generales de serialización
497+
498+
Ya hemos dicho que algunas características generales de la serialización
499+
independientes al formato de traducción puede configurarse antes de crear el
500+
mapeador. Hay, sin embargo, otro mecanismo más potente para definir la
501+
serialización hacia el formato: definir un |ObjectWriter|.
502+
503+
.. code-block:: java
504+
:emphasize-lines: 7
505+
506+
ObjectMapper mapper = JsonMapper.builder().build(); // Mapper inmutable.
507+
ObjectWriter writer = mapper.writer()
508+
.with(Serialization.INDENT_OUTPUT);
509+
510+
// Ahora usamos el writer (en vez del mapper) para escribir los datos
511+
512+
writer.writeValue(st, datos);
513+
514+
.. _jackson-writer-wrapper:
515+
516+
.. rubric:: Filtrado de atributos.
517+
518+
Puede darse el caso de que no quiera almacenarse toda la información contenida
519+
en el modelo de datos. La solución general es crear |DTO|\ s, pero *Jackson*
520+
tiene algunas herramientas que nos permiten ahorrarnos la definición de estos
521+
objetos.
522+
523+
En concreto, nos facilita dos herramientas: las **vistas**, que permiten hacer un
524+
filtrado estático basado en anotaciones, y los **filtros**, que permiten la
525+
definición de filtrados programáticos. Para ilustrarlas supongamos que tenemos
526+
más información referente a los alumnos:
527+
528+
.. code-block:: java
529+
:emphasize-lines: 4, 5
530+
531+
public class Alumno implements Serializable {
532+
private String nombre;
533+
private LocalDate fechaNacimiento;
534+
private int telefono;
535+
private String dni;
536+
537+
// Resto de la definición de la clase.
538+
}
539+
540+
Los datos de los alumnos no tienen la misma confidencialidad, así que nos podría
541+
interesar proporcionar más o menos información. Para ello se definen tres
542+
*vistas* de menor a mayor confidencialidad:
543+
544+
+ Views.Public
545+
+ Views.Internal
546+
+ Views.Admin
547+
548+
Al anotar la clase podemos indicar a qué vista queremos que pertenezca el
549+
atributo:
550+
551+
.. code-block:: java
552+
:emphasize-lines: 2, 8, 11
553+
554+
public class Alumno implements Serializable {
555+
@JsonView(Views.Public.class)
556+
private String nombre;
557+
558+
// No pertenece a ninguna vista así que se muestra siempre.
559+
private LocalDate fechaNacimiento;
560+
561+
@JsonView(Views.Internal.class)
562+
private int telefono;
563+
564+
@JsonView(Views.Admin.class)
565+
private String dni;
566+
}
567+
568+
Definida la *vista* de cada atributo, en el momento de serializar podemos
569+
indicar al objeto |ObjectWriter| qué vista debe escribir. En nuestro ejemplo:
570+
571+
+ Sin vista, sólo se serializará sólo ``fechaNacimiento``.
572+
+ Con Views.Public se serializará ``fechaNacimiento`` y ``nombre``.
573+
+ Con Views.Internal se añadirá a las anteriores el teléfono.
574+
+ Con Views.Admin se serializarán todos los atributos.
575+
576+
Para escribir con vistas, simplemente, debemos crear un *writer* y ajustar la
577+
vista:
578+
579+
.. code-block:: java
580+
:emphasize-lines: 2
581+
582+
ObjectWriter writer = mapper.writer()
583+
.withWithView(Views.Public.class);
584+
585+
writer.writeValueAsString(grupos);
586+
587+
El otro mecanismo para filtrar atributos es usar **filtros**:
588+
589+
.. code-block:: java
590+
:emphasize-lines: 1-3, 7
591+
592+
FilterProvider filters = new SimpleFilterProvider()
593+
.addFilters("filtro",
594+
SimpleBeanPropertyFilter.serializeAllExcept("telefono", "dni"));
595+
596+
// También existe .filterOutAllExcept
597+
598+
ObjectWriter writer = mapper.writer(filters);
599+
writer.writeAsValueAsString(grupos);
600+
601+
476602
.. rubric:: Notas al pie
477603

478604
.. [#] Siempre que el tipo, claro está, sea un primitivo de |JSON| (p. ej. una
@@ -504,6 +630,7 @@ anotación individual en cada uno de los atributos y hacer lo siguiente:
504630
.. |LocalDate| replace:: :java-time:`LocalDate <LocalDate>`
505631
.. |XML| replace:: :abbr:`XML (eXtensible Markup Language)`
506632
.. |YAML| replace:: :abbr:`YAML (YAML Ain't Markup Language)`
633+
.. |DTO| replace:: :abbr:`DTO (Data Transfer Object)`
507634

508635
.. |JsonMapper| replace:: :jackson-databind:`JsonMapper <json/JsonMapper>`
509636
.. |JsonFactory| replace:: :jackson-core:`JsonFactory <json/JsonFactory>`
@@ -517,4 +644,4 @@ anotación individual en cada uno de los atributos y hacer lo siguiente:
517644
.. |Alumno| replace:: :ref:`Alumno <class-alumno>`
518645
.. |LocalDateSerializer| replace:: :jackson-databind:`LocalDateSerializer <ext/javatime/ser/LocalDateSerializer>`
519646
.. |LocalDateDeserializer| replace:: :jackson-databind:`LocalDateDeserializer <ext/javatime/deser/LocalDateDeserializer>`
520-
647+
.. |ObjectWriter| replace:: :jackson-databind:`ObjectWriter <ObjectWriter>`

source/03.xml/02.jackson.rst

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -206,19 +206,39 @@ Otra gran diferencia respecto a la traducción a |JSON| es que en |XML| nos hemo
206206
visto obligados a crear un elemento contenedor de la secuencia llamado
207207
``<centro>``. No contiene más información que la propia secuencia, por lo que en
208208
el modelo de datos no se traduce en una nueva clase. En *Java*, simplemente,
209-
existirá un dato de tipo ``Grupo[]`` o ``List<Grupo>``.
209+
existirá un dato de tipo ``Grupo[]`` o ``List<Grupo>``. Si intentáramos traducir
210+
directamente estos tipos de datos a |XML|, la librería no nos permitiría definir
211+
el nombre de la etiqueta para el envoltorio (queremos que sea ``<centro>``) ni
212+
tampoco qué etiqueta tendría cada uno de los elementos de la secuencia (utiliza
213+
el predeterminado ``<item>``) por lo que obtendríamos una salida semejante a
214+
esta:
210215

211-
Por eso, para poder hacer la traducción a |XML| necesitamos añadir una clase
212-
envoltorio auxiliar:
216+
.. code-block:: xml
217+
218+
<List1> <!-- O alguna etiqueta semejante -->
219+
<item nivel="1" etapa="2" grupo="D">
220+
<!-- Elementos del grupo (nombrados correctamente) -->
221+
</item>
222+
223+
<!-- Otro grupos con la etiqueta item -->
224+
</List1>
225+
226+
Para solucionar esto tenemos dos soluciones:
227+
228+
.. rubric:: Clase envoltorio auxiliar
213229

214230
.. code-block:: java
215231
:emphasize-lines: 1, 3, 4
216232
217233
@JsonRootName("centro")
218-
private class GrupoWrapper {
234+
private class GruposWrapper {
219235
@JacksonXmlElementWrapper(useWrapping = false)
220236
@JacksonXmlProperty(localName = "grupo")
221-
private Grupo[] grupos;
237+
public Grupo[] grupos;
238+
239+
public GruposWrapper(Grupo[] grupos) {
240+
this.grupos = grupos;
241+
}
222242
}
223243
224244
Las particularidades de este código son las siguientes:
@@ -233,6 +253,37 @@ Las particularidades de este código son las siguientes:
233253
+ El nombre del elemento es ``<grupo>``, no ``<grupos>``, así que es necesaria
234254
también la anotación ``@JacksonXmlProperty``.
235255

256+
Una vez definida la clase, basta con que sea una instancia de ella la que se
257+
escriba (o lea):
258+
259+
.. code-block:: java
260+
261+
// Escritura
262+
mapper.writeValue(new GruposWrapper(grupos));
263+
// Lectura
264+
265+
Grupo[] grupos = mapper.readValue(st, GruposWrapper.class).grupos;
266+
267+
.. rubric:: Mapa envoltorio
268+
269+
Consiste en improvisar un mapa con una única clave que sea el nombre que
270+
queremos darle a los elementos de la secuencia:
271+
272+
.. code-block:: java
273+
:emphasize-lines: 1, 4
274+
275+
Map<String, Object> centro = Map.of("grupo", grupos); // Se usará <grupo>, no <item>
276+
277+
ObjectWriter writer = mapper.writer()
278+
.withRootName("centro"); // Indicamos que se use <centro>, no <Map1>
279+
280+
281+
// Escritura
282+
writer.writeValues(st, centro);
283+
284+
// Lectura (no tiene dificultad)
285+
mapper.readValues(st, Grupo[].class);
286+
236287
Traductores
237288
-----------
238289
En principio, como en el caso de |JSON| necesitamos el traductor para ``Etapa``.
@@ -273,7 +324,7 @@ igual que :ref:`la escritura en el caso de JSON <json-jackson-write>`:
273324

274325
.. code-block:: java
275326
276-
Path archivo = Path.of(System.getProperty("java.io.tmpdir"), "claustro.xml");
327+
Path archivo = Path.of(System.getProperty("java.io.tmpdir"), "grupos.xml");
277328
278329
Grupo[] grupos = new Grupo[] {
279330
new Grupo(1, Etapa.ESO, 'A', New Tutor("Florencio Vázquez Méndez", "Inglés"), {
@@ -292,13 +343,12 @@ igual que :ref:`la escritura en el caso de JSON <json-jackson-write>`:
292343
293344
MapperBuilder<?, ?> builder = XmlMapper.builder(factory)
294345
.enable(SerializationFeature.INDENT_OUTPUT)
295-
.addMixIn(Claustro.class, ClaustroMixin.class)
296-
.addMixIn(Profesor.class, ProfesorMixin.class);
346+
.addMixIn(Grupo.class, GrupoMixin.class);
297347
298348
ObjectMapper mapper = builder.build();
299349
300350
try (OutputStream st = Files.newOutputStream(archivo)) {
301-
mapper.writeValue(st, claustro); // Puede generar JacksonException
351+
mapper.writeValue(st, new GruposWrapper(grupos)); // Puede generar JacksonException
302352
} catch(JacksonException | IOException err) {
303353
err.printStackTrace();
304354
}
@@ -309,7 +359,7 @@ También podríamos haber generado una cadena con la salida:
309359
.. code-block:: java
310360
311361
try {
312-
String contenido = mapper.writeValueAsString(claustro);
362+
String contenido = mapper.writeValueAsString(new GruposWrapper(grupos));
313363
System.out.println(contenido);
314364
} catch(JacksonException err) {
315365
err.printStackTrace();
@@ -332,17 +382,16 @@ lectura del formato es :ref:`prácticamente la misma que para JSON <json-jackson
332382

333383
.. code-block:: java
334384
335-
Path archivo = Path.of(System.getProperty("user.home"), "claustro.xml");
385+
Path archivo = Path.of(System.getProperty("user.home"), "grupos.xml");
336386
337387
MapperBuilder<?, ?> builder = XmlFactory.builder()
338-
.addMixIn(Claustro.class, ClaustroMixin.class)
339-
.addMixIn(Profesor.class, ProfesorMixin.class);
388+
.addMixIn(Grupo.class, GrupoMixin.class)
340389
341390
ObjectMapper mapper = builder.build();
342391
343392
try (InputStream st = Files.newInputStream(ruta)) {
344-
Claustro claustro = mapper.readValue(st, Claustro.class);
345-
System.out.println(claustro);
393+
Grupos[] grupos = mapper.readValue(st, GruposWrapper.class).grupos;
394+
System.out.println(grupos);
346395
} catch(JacksonIOException | IOException err) {
347396
err.printStackTrace();
348397
}

0 commit comments

Comments
 (0)