Todas las técnicas de un vistazo
Del IV al IX leemos el mismo menú y sacamos la misma salida. Lo único que cambia es cómo se recorre el fichero.
Los cuatro modelos la idea clave
Lo cargo todo
Se lee el documento entero y se guarda en memoria como un árbol. Puedes ir a cualquier nodo, en cualquier orden.
Fácil, pero gasta mucha RAM.
El parser me llama
El parser lee de arriba abajo y va ejecutando tus métodos: startElement, characters, endElement.
Poca memoria, código más lioso.
Yo le pido
Tu programa pide el siguiente trozo con next() dentro de un bucle. Tú decides cuándo avanzar o parar.
Poca memoria y más legible que SAX.
Lo convierte solo
Describes el formato con clases y anotaciones, y la librería pasa de fichero a objetos (y al revés) en una línea.
Lo más rápido de programar. Necesita Maven.
Tabla comparativa para el examen
| Ej. | Técnica | Formato | Modelo | Quién controla | Memoria | Escribe | Clases propias | Maven |
|---|---|---|---|---|---|---|---|---|
| III | Serialización | binario | Objetos enteros | Java | Lo que ocupen | Sí | Llibre, GestorLlibres | No |
| IV | DOM | XML | Árbol | Tú navegas el árbol | Alta (5–10× el fichero) | Sí, con Transformer | Plat | No |
| V | Gson | JSON | Binding | Gson | Alta | Sí, toJson | Menu, Plat, Descripcio, Grup | Sí |
| VI | SAX | XML | Push | El parser | Muy baja | No | Plat, MenuHandler | No |
| VII | StAX | XML | Pull | Tu bucle next() | Muy baja | No (en el ejercicio) | Plat | No |
| VIII | Jackson | JSON | Binding (o pull) | ObjectMapper | Media | Sí, writeValue | Menu, Plat, Descripcio, Grup + 2 transformadas | Sí |
| IX | JAXB | XML | Binding | JAXB (usa SAX/StAX) | Media | Sí, marshal | Menu, Plat, Descripcio, Grup + 3 transformadas | Sí |
¿Cuál uso? regla rápida
Según lo que necesito
- Guardar objetos Java y recuperarlos en el mismo programa → Serialización.
- Fichero pequeño que quiero consultar o modificar en cualquier orden → DOM.
- Fichero enorme, solo leer de principio a fin → SAX.
- Fichero grande pero quiero código claro o parar a mitad → StAX.
- Formato conocido y quiero trabajar con mis clases → Gson / Jackson (JSON) o JAXB (XML).
Parsing y binding
Parsing: convertir el texto del fichero en una estructura genérica (árbol, eventos, tokens).
Binding: pasar automáticamente esa estructura a objetos Java (POJOs) y al revés.
Puede haber parsing sin binding (DOM, SAX, StAX), pero no binding sin parsing.
Los ficheros de datos el menú de todos los ejercicios
Cada plato tiene Descripcio y Tipus dos veces: una del plato y otra dentro de Grup. Esa repetición es la que obliga a usar dinsGrup en SAX y StAX.
<?xml version="1.0" encoding="UTF-8"?>
<Menu>
<Plat>
<Descripcio codi="A01" preu="10">Macarrons</Descripcio>
<Tipus>Plat Principal</Tipus>
<Grup id="01">
<Descripcio>Aliments farinosos</Descripcio>
<Tipus>Pasta</Tipus>
</Grup>
</Plat>
<Plat>
<Descripcio codi="A35" preu="3">Macedònia</Descripcio>
<Tipus>Postre</Tipus>
<Grup id="03">
<Descripcio>Origen vegetal</Descripcio>
<Tipus>Fruita</Tipus>
</Grup>
</Plat>
<!-- ... A24 Pastís de formatge, A56 Vedella amb bolets -->
</Menu>{
"Menu": [
{
"Descripcio": {
"codi": "A01",
"preu": 10,
"text": "Macarrons"
},
"Tipus": "Plat Principal",
"Grup": {
"id": "01",
"Descripcio": "Aliments farinosos",
"Tipus": "Pasta"
}
}
// ... A35, A24, A56
]
}Montar el proyecto en Eclipse lo que nos dio guerra
Sin Maven (III, IV, VI, VII)
DOM, SAX y StAX vienen incluidos en Java. Basta con File → New → Java Project.
Si Eclipse crea module-info.java, bórralo.
Con Maven (V, VIII, IX)
- Haz clic en un hueco vacío del Project Explorer antes de crear el proyecto.
- Marca Create a simple project y Use default Workspace location.
- Packaging = jar, no
pom: conpomno se compila tu código. - Parent Project vacío.
- Tras tocar el
pom.xml: Maven → Update Project.
El fichero de datos va en la raíz del proyecto, al lado de pom.xml y fuera de src. Si no, sale El fitxer o directori no existeix. Los ficheros que genera el programa aparecen ahí después de pulsar F5.
Serialización y deserialización
Guardamos un catálogo de libros entero en un fichero con una sola llamada y lo recuperamos al volver a abrir el programa.
Qué hace
Serializar es convertir un objeto (y todos los objetos que contiene) en una secuencia de bytes. Deserializar es reconstruirlo con el mismo estado a partir de esos bytes.
No hay que pensar en posiciones ni recorrer nada: se escribe el objeto y se lee el objeto.
Cómo funciona
- La clase implementa
Serializabley defineserialVersionUID. - Para guardar:
ObjectOutputStream.writeObject(lista). - Para cargar:
ObjectInputStream.readObject()y cast al tipo original.
No es un formato de intercambio: el fichero es binario y solo lo entiende Java. Sirve para guardar el estado del programa, no para pasar datos a otras aplicaciones (para eso, JSON o XML).
Código clave
// GUARDAR (serializar)
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cataleg.dat"))) {
oos.writeObject(cataleg);
}
// CARGAR (deserializar): readObject() devuelve Object → cast
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cataleg.dat"))) {
cataleg = (ArrayList<Llibre>) ois.readObject();
}Palabras clave
serialVersionUID: versión de la clase. Si cambia y no coincide →InvalidClassException.transient: ese atributo no se guarda (datos sensibles o calculables).- Los atributos
staticno se guardan: son de la clase, no del objeto. - Si la clase padre es serializable, las hijas también.
Errores típicos
NotSerializableException: olvidasteimplements Serializable.ClassNotFoundException: hay que capturarla enreadObject().- Nunca deserialices datos de origen desconocido: es un riesgo de seguridad.
Clases que necesitas
| Clase | Para qué sirve |
|---|---|
| Llibre | El objeto que guardamos. implements Serializable. |
| GestorLlibres | Tiene el ArrayList y los métodos añadir, mostrar, guardar y cargar. |
| Main | Primera ejecución: crea y guarda. Segunda: carga y muestra. |
exercici3/ ├── cataleg.dat ← lo crea el programa └── src/exercici3/ ├── Llibre.java ├── GestorLlibres.java └── Main.java
Ejercicio completo solución de ejemplo
Este ejercicio no lo programamos paso a paso; esta solución está probada y cumple el enunciado.
Main.javaArranca el programa
package exercici3;
import java.io.File;
public class Main {
public static void main(String[] args) {
GestorLlibres gestor = new GestorLlibres();
String fitxer = "cataleg.dat";
if (!new File(fitxer).exists()) {
// 1a ejecución: no hay fichero → creamos libros y los guardamos
gestor.afegirLlibre(new Llibre("El Quixot", "Cervantes", 1605, 19.90));
gestor.afegirLlibre(new Llibre("Mecanoscrit del segon origen", "Pedrolo", 1974, 9.50));
gestor.guardarCataleg(fitxer);
System.out.println("Catàleg creat i guardat a " + fitxer);
} else {
// 2a ejecución: el fichero existe → lo cargamos (deserializamos)
gestor.carregarCataleg(fitxer);
System.out.println("Catàleg carregat de " + fitxer);
}
gestor.mostrarCataleg();
}
}Llibre.javaEl objeto serializable
package exercici3;
import java.io.Serializable;
// Un libro del catálogo. Implementa Serializable para poder guardarlo en un fichero binario
public class Llibre implements Serializable {
// Versión de la clase: si cambia la clase y no coincide, salta InvalidClassException
private static final long serialVersionUID = 1L;
private String titol; // título del libro
private String autor; // autor
private int anyPublicacio; // año de publicación
private double preu; // precio
// Constructor con todos los datos
public Llibre(String titol, String autor, int anyPublicacio, double preu) {
this.titol = titol;
this.autor = autor;
this.anyPublicacio = anyPublicacio;
this.preu = preu;
}
// Getters
public String getTitol() { return titol; }
public String getAutor() { return autor; }
public int getAnyPublicacio() { return anyPublicacio; }
public double getPreu() { return preu; }
// Cómo se muestra el libro por pantalla
@Override
public String toString() {
return titol + " — " + autor + " (" + anyPublicacio + ") — " + preu + "€";
}
}GestorLlibres.javaGuardar y cargar
package exercici3;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
// Gestiona el catálogo: añadir, mostrar, guardar y cargar
public class GestorLlibres {
// Lista de libros. ArrayList ya es Serializable
private ArrayList<Llibre> cataleg = new ArrayList<>();
// Añade un libro al catálogo
public void afegirLlibre(Llibre l) {
cataleg.add(l);
}
// Muestra todos los libros por pantalla
public void mostrarCataleg() {
for (Llibre l : cataleg) {
System.out.println(l);
}
}
// SERIALIZAR: guarda TODO el catálogo en un fichero binario de una vez
public void guardarCataleg(String fitxer) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fitxer))) {
oos.writeObject(cataleg);
} catch (IOException e) {
System.err.println("Error guardant: " + e.getMessage());
}
}
// DESERIALIZAR: lee el fichero y reconstruye el catálogo
@SuppressWarnings("unchecked")
public void carregarCataleg(String fitxer) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fitxer))) {
// readObject() devuelve Object: hay que hacer cast al tipo original
cataleg = (ArrayList<Llibre>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
System.err.println("Error carregant: " + e.getMessage());
}
}
}Resultado
$ 1a ejecución Catàleg creat i guardat a cataleg.dat El Quixot — Cervantes (1605) — 19.9€ Mecanoscrit del segon origen — Pedrolo (1974) — 9.5€ $ 2a ejecución Catàleg carregat de cataleg.dat El Quixot — Cervantes (1605) — 19.9€ Mecanoscrit del segon origen — Pedrolo (1974) — 9.5€
Parser DOM XML
Cargamos MenuXML.xml entero en memoria como un árbol y lo recorremos para crear un Plat por cada plato.
Qué hace
DOM (Document Object Model) lee el XML entero y construye un árbol de nodos en memoria: Document, Element, Text, Attr…
Con el árbol cargado puedes ir a cualquier nodo, consultarlo, modificarlo y volver a guardar el XML.
Cómo funciona
DocumentBuilderFactory→DocumentBuilder.builder.parse(fichero)construye el árbol.normalize()limpia los nodos de texto vacíos.getElementsByTagName("Plat")devuelve unaNodeList.- Atributos con
getAttribute(), texto congetTextContent().
Es el único parser que tiene todo el documento a la vez: puedes ir hacia atrás, saltar y modificar. A cambio, gasta mucha memoria (entre 5 y 10 veces el tamaño del fichero). Ideal para ficheros pequeños o medianos.
Código clave
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(new File("MenuXML.xml"));
doc.getDocumentElement().normalize();
NodeList nodesPlat = doc.getElementsByTagName("Plat");
Element elPlat = (Element) nodesPlat.item(0);
Element elDesc = (Element) elPlat.getElementsByTagName("Descripcio").item(0);
String codi = elDesc.getAttribute("codi"); // atributo
String nom = elDesc.getTextContent(); // texto de dentroLa trampa del ejercicio: getElementsByTagName busca en todos los descendientes. Dentro de un Plat hay dos Descripcio: item(0) es la del plato. Para la del grupo, busca dentro del elemento Grup.
Métodos para modificar (por si cae en el examen)
setTextContent, setAttribute, appendChild, removeChild, replaceChild. Para guardar los cambios en un fichero se usa un Transformer.
Clases que necesitas
| Clase | Para qué sirve |
|---|---|
| Plat | POJO «plano» con los 7 datos del plato y su toString(). |
| Main | Carga el árbol, lo recorre y llena el array de Plat. |
exercici4/ ├── MenuXML.xml ← en la raíz └── src/exercici4/ ├── Main.java └── Plat.java
Ejercicio completo solución de ejemplo
Resuelve la Parte II (con POJO), que ya incluye la salida de la Parte I. Probado con el menú de clase.
Main.javaRecorre el árbol DOM
package exercici4;
import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class Main {
public static void main(String[] args) {
try {
// 1. Creamos el parser DOM (fábrica → builder)
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 2. Cargamos TODO el XML en memoria como un árbol
Document doc = builder.parse(new File("MenuXML.xml"));
doc.getDocumentElement().normalize(); // limpia nodos de texto vacíos
// 3. Buscamos todas las etiquetas <Plat>
NodeList nodesPlat = doc.getElementsByTagName("Plat");
Plat[] plats = new Plat[nodesPlat.getLength()];
// 4. Recorremos cada <Plat> y sacamos sus datos
for (int i = 0; i < nodesPlat.getLength(); i++) {
Element elPlat = (Element) nodesPlat.item(i);
// item(0) = la primera <Descripcio> dentro de <Plat> → la del plato
Element elDesc = (Element) elPlat.getElementsByTagName("Descripcio").item(0);
Element elGrup = (Element) elPlat.getElementsByTagName("Grup").item(0);
Plat p = new Plat();
p.setCodi(elDesc.getAttribute("codi")); // atributo
p.setPreu(Integer.parseInt(elDesc.getAttribute("preu"))); // atributo
p.setNom(elDesc.getTextContent().trim()); // texto
p.setTipus(elPlat.getElementsByTagName("Tipus").item(0).getTextContent().trim());
// Dentro de <Grup> buscamos SUS propias <Descripcio> y <Tipus>
p.setGrupId(elGrup.getAttribute("id").trim());
p.setGrupDescripcio(elGrup.getElementsByTagName("Descripcio").item(0).getTextContent().trim());
p.setGrupTipus(elGrup.getElementsByTagName("Tipus").item(0).getTextContent().trim());
plats[i] = p;
}
// 5. Mostramos todos los platos con su toString()
System.out.println("=== Menú ===");
for (Plat p : plats) {
System.out.println(p);
}
} catch (Exception e) {
System.err.println("Error llegint el fitxer: " + e.getMessage());
}
}
}Plat.javaPOJO del plato
package exercici4;
// Representa un plato del menú con todos sus datos
public class Plat {
private String codi; // código del plato (A01, A35...)
private int preu; // precio del plato
private String nom; // nombre del plato (Macarrons...)
private String tipus; // tipo de plato (Plat Principal, Postre...)
private String grupId; // id del grupo (01, 02...)
private String grupDescripcio; // descripción del grupo (Aliments farinosos...)
private String grupTipus; // tipo del grupo (Pasta, Fruita...)
// Constructor vacío: creamos el plato y lo vamos rellenando mientras leemos el XML
public Plat() {
}
// Getters y setters
public String getCodi() { return codi; }
public void setCodi(String codi) { this.codi = codi; }
public int getPreu() { return preu; }
public void setPreu(int preu) { this.preu = preu; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
public String getGrupId() { return grupId; }
public void setGrupId(String grupId) { this.grupId = grupId; }
public String getGrupDescripcio() { return grupDescripcio; }
public void setGrupDescripcio(String grupDescripcio) { this.grupDescripcio = grupDescripcio; }
public String getGrupTipus() { return grupTipus; }
public void setGrupTipus(String grupTipus) { this.grupTipus = grupTipus; }
// Muestra el plato con el formato que pide el ejercicio
// [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos)
@Override
public String toString() {
return "[" + codi + "] " + nom
+ " — " + preu + "€"
+ " — " + tipus
+ " — (" + grupTipus + ", " + grupDescripcio + ")";
}
}Resultado
=== Menú === [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos) [A35] Macedònia — 3€ — Postre — (Fruita, Origen vegetal) ...
Parser DOM JSON con Gson
Gson convierte MenuJSON.json en objetos Java él solo. Añadimos un plato y lo guardamos en MenuJSON_modificat.json.
Qué hace
Binding automático: lees el fichero con una línea y obtienes objetos con sus datos. Para escribir, igual: una línea y Gson genera el JSON.
Para que funcione, tus clases tienen que copiar la estructura del JSON.
Cómo funciona
- Añades Gson al
pom.xml. - Creas una clase por cada objeto del JSON:
Menu,Plat,Descripcio,Grup. @SerializedNamedonde el nombre del JSON no coincide con el del atributo.- Leer:
gson.fromJson(reader, Menu.class). - Escribir:
gson.toJson(menu, writer).
No recorres nada: la estructura de tus clases ES el formato. A diferencia de SAX o StAX, aquí necesitas una clase por cada nivel del JSON, porque Gson las rellena él solo.
Cómo encaja el JSON con las clases
| En el JSON | En Java | ¿Hace falta @SerializedName? |
|---|---|---|
| "Menu": [ … ] | List<Plat> plats | Sí: nombre distinto |
| "Descripcio": { … } | Descripcio descripcio | Sí: mayúscula |
| "Tipus": "…" | String tipus | Sí: mayúscula |
| "Grup": { … } | Grup grup | Sí: mayúscula |
| "codi", "preu", "text", "id" | codi, preu, text, id | No: ya coinciden |
Código clave
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
// JSON → objetos
Menu menu = gson.fromJson(reader, Menu.class);
// añadir un plato
menu.afegirPlat(new Plat(new Descripcio("A70", 8, "Amanida verda"), "Primer Plat",
new Grup("03", "Origen vegetal", "Verdura")));
// objetos → JSON
gson.toJson(menu, writer);Reglas de las clases
- Constructor vacío (Gson lo usa para crear el objeto).
- Getters (los usa tu
toString()). - Mismos nombres que el JSON, o
@SerializedName.
Errores que nos salieron
- getPreu() en rojo: faltaban los getters en
Descripcio. - Path.of no existe: Eclipse usaba un Java antiguo. Solución:
Paths.get(...). - No compila nada:
<packaging>pom</packaging>→ cambiar ajar.
Clases que necesitas
| Clase | Representa |
|---|---|
| Menu | { "Menu": [ … ] }: la lista de platos. |
| Plat | Un plato: Descripcio + Tipus + Grup. |
| Descripcio | { "codi", "preu", "text" } |
| Grup | { "id", "Descripcio", "Tipus" } |
| Main | Lee, muestra, añade y guarda. |
exercici5/ ├── pom.xml ├── MenuJSON.json ← en la raíz ├── MenuJSON_modificat.json ← lo crea el programa └── src/main/java/exercici5/ ├── Main.java ├── Menu.java ├── Plat.java ├── Descripcio.java └── Grup.java
Ejercicio completo
pom.xmlDependencia Gson
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>exercici5</groupId>
<artifactId>exercici5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- jar, NO pom: si no, Maven no compila tus clases -->
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</project>Main.javaLeer, añadir y guardar
package exercici5;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) {
// Creamos Gson: el JSON sale bien ordenado y con los acentos normales
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.create();
// 1. Leemos el fichero JSON y Gson lo convierte en objetos Java
Menu menu;
try (Reader reader = Files.newBufferedReader(Paths.get("MenuJSON.json"), StandardCharsets.UTF_8)) {
menu = gson.fromJson(reader, Menu.class);
} catch (IOException e) {
System.err.println("Error llegint el fitxer: " + e.getMessage());
return;
}
// Mostramos todos los platos por consola
System.out.println("=== Menú ===");
for (Plat p : menu.getPlats()) {
System.out.println(p);
}
// 2. Creamos un plato nuevo y lo añadimos al menú
menu.afegirPlat(new Plat(
new Descripcio("A70", 8, "Amanida verda"),
"Primer Plat",
new Grup("03", "Origen vegetal", "Verdura")));
// 3. Convertimos el menú otra vez a JSON y lo guardamos en un fichero nuevo
try (Writer writer = Files.newBufferedWriter(Paths.get("MenuJSON_modificat.json"), StandardCharsets.UTF_8)) {
gson.toJson(menu, writer);
System.out.println("\nGuardat a MenuJSON_modificat.json");
} catch (IOException e) {
System.err.println("Error escrivint el fitxer: " + e.getMessage());
}
}
}Menu.javaRaíz del JSON
package exercici5;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
// Representa todo el menú del JSON: { "Menu": [ ...platos... ] }
public class Menu {
// Lista de platos. "Menu" es el nombre que tiene en el JSON
@SerializedName("Menu")
private List<Plat> plats = new ArrayList<>();
// Constructor vacío (Gson lo necesita para crear el objeto)
public Menu() {}
// Getters y setters
public List<Plat> getPlats() { return plats; }
public void setPlats(List<Plat> plats) { this.plats = plats; }
// Añade un plato a la lista
public void afegirPlat(Plat plat) { plats.add(plat); }
}Plat.javaUn plato
package exercici5;
import com.google.gson.annotations.SerializedName;
// Representa un plato del menú
public class Plat {
// Descripción del plato (código, precio y nombre)
@SerializedName("Descripcio")
private Descripcio descripcio;
// Tipo de plato (Plat Principal, Postre...)
@SerializedName("Tipus")
private String tipus;
// Grupo de alimentos al que pertenece
@SerializedName("Grup")
private Grup grup;
// Constructor vacío (Gson lo necesita)
public Plat() {}
// Constructor para crear platos nuevos
public Plat(Descripcio descripcio, String tipus, Grup grup) {
this.descripcio = descripcio;
this.tipus = tipus;
this.grup = grup;
}
// Getters y setters
public Descripcio getDescripcio() { return descripcio; }
public void setDescripcio(Descripcio descripcio) { this.descripcio = descripcio; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
public Grup getGrup() { return grup; }
public void setGrup(Grup grup) { this.grup = grup; }
// Muestra el plato con el formato que pide el ejercicio
// [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos)
@Override
public String toString() {
return "[" + descripcio.getCodi() + "] " + descripcio.getText()
+ " — " + descripcio.getPreu() + "€"
+ " — " + tipus
+ " — (" + grup.getTipus() + ", " + grup.getDescripcio() + ")";
}
}Descripcio.javacodi, preu, text
package exercici5;
import com.google.gson.annotations.SerializedName;
// Representa la descripción de un plato: { "codi": "A01", "preu": 10, "text": "Macarrons" }
public class Descripcio {
// Código del plato (A01, A35...)
@SerializedName("codi")
private String codi;
// Precio del plato
@SerializedName("preu")
private int preu;
// Nombre del plato
@SerializedName("text")
private String text;
// Constructor vacío (Gson lo necesita)
public Descripcio() {}
// Constructor con todos los datos
public Descripcio(String codi, int preu, String text) {
this.codi = codi;
this.preu = preu;
this.text = text;
}
// Getters y setters
public String getCodi() { return codi; }
public void setCodi(String codi) { this.codi = codi; }
public int getPreu() { return preu; }
public void setPreu(int preu) { this.preu = preu; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}Grup.javaid, Descripcio, Tipus
package exercici5;
import com.google.gson.annotations.SerializedName;
// Representa el grupo de alimentos: { "id": "01", "Descripcio": "Aliments farinosos", "Tipus": "Pasta" }
public class Grup {
// Identificador del grupo
@SerializedName("id")
private String id;
// Descripción del grupo (Aliments farinosos, Lactis...)
@SerializedName("Descripcio")
private String descripcio;
// Tipo del grupo (Pasta, Fruita...)
@SerializedName("Tipus")
private String tipus;
// Constructor vacío (Gson lo necesita)
public Grup() {}
// Constructor con todos los datos
public Grup(String id, String descripcio, String tipus) {
this.id = id;
this.descripcio = descripcio;
this.tipus = tipus;
}
// Getters y setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getDescripcio() { return descripcio; }
public void setDescripcio(String descripcio) { this.descripcio = descripcio; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
}Resultado
=== Menú === [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos) [A35] Macedònia — 3€ — Postre — (Fruita, Origen vegetal) [A24] Pastís de formatge — 6€ — Postre — (Dolços, Lactis) [A56] Vedella amb bolets — 12€ — Plat Principal — (Carn, Origen animal) Guardat a MenuJSON_modificat.json
Parser SAX XML
SAX lee MenuXML.xml de arriba abajo y nos va avisando. Nosotros montamos los Plat sobre la marcha.
Qué hace
SAX (Simple API for XML) recorre el documento una sola vez, sin guardarlo en memoria. Cada vez que encuentra algo, ejecuta un método tuyo (un callback).
Es como un profesor que lee en voz alta sin parar: tú solo reaccionas a lo que dice.
Cómo funciona
SAXParserFactory→SAXParser.- Creas una clase que
extends DefaultHandler. - Sobrescribes
startElement,charactersyendElement. parser.parse(fichero, handler): SAX llama a tus métodos solo.- Al final pides la lista al handler.
El parser manda (push). Tú no decides cuándo avanzar: él te empuja los datos. Por eso necesitas una clase aparte (el handler) y variables para recordar dónde estás. Gasta muy poca memoria y sirve para ficheros enormes, pero es el más lioso de programar.
Los tres métodos abrir · texto · cerrar
| Parte | Método | Qué sabes ahí | Qué hacemos |
|---|---|---|---|
| <Tipus> | startElement() | El nombre de la etiqueta y sus atributos | Crear el plato, leer codi, preu, id |
| Pasta | characters() | El texto (puede llegar a trozos) | Guardarlo en el StringBuilder |
| </Tipus> | endElement() | Que la etiqueta ha terminado | Meter el texto completo en el atributo correcto |
El interruptor dinsGrup saber dónde estamos
SAX solo dice «se ha cerrado Tipus», sin decir si es el del plato o el del grupo. dinsGrup se enciende al abrir <Grup> y se apaga al cerrarlo:
<Plat>false <Descripcio …>Macarrons</Descripcio>false → plato <Tipus>Plat Principal</Tipus>false → plato <Grup id="01">true <Descripcio>Aliments farinosos</Descripcio>true → grupo <Tipus>Pasta</Tipus>true → grupo </Grup>false</Plat>false → addcase "Tipus":
if (dinsGrup) {
platActual.setGrupTipus(valor); // dentro de <Grup> → "Pasta"
} else {
platActual.setTipus(valor); // fuera de <Grup> → "Plat Principal"
}
break;Sin el interruptor, «Pasta» machacaría a «Plat Principal». La otra variable de contexto es platActual: la «caja» que se crea en <Plat> y se guarda en la lista en </Plat>.
Errores que nos salieron
Todos los @Override en rojo: faltaba extends DefaultHandler en la clase.
Grupo con datos mezclados: en el case "Tipus" se usaba setGrupDescripcio en vez de setGrupTipus.
Clases que necesitas
| Clase | Para qué sirve |
|---|---|
| Plat | POJO plano con los 7 datos y el toString(). No hacen falta Descripcio ni Grup: sin binding, tú decides dónde guardar cada dato. |
| MenuHandler | extends DefaultHandler. Recibe los eventos y monta los platos. |
| Main | Crea el parser, lanza parse() y muestra el array. |
exercici6/ ├── MenuXML.xml ← en la raíz └── src/exercici6/ ├── Main.java ├── MenuHandler.java └── Plat.java
Ejercicio completo
MenuHandler.javaRecibe los eventos SAX
package exercici6;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
// Clase que recibe los avisos (eventos) del parser SAX mientras lee el XML
public class MenuHandler extends DefaultHandler {
private List<Plat> plats = new ArrayList<>(); // aquí guardamos los platos leídos
private Plat platActual; // plato que estamos leyendo ahora
private boolean dinsGrup = false; // true si estamos dentro de <Grup>
private StringBuilder text = new StringBuilder(); // texto que hay dentro de una etiqueta
// Devuelve la lista de platos cuando ya se ha leído el XML
public List<Plat> getPlats() {
return plats;
}
// SAX llama a este método cada vez que se ABRE una etiqueta, por ejemplo <Plat>
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
text.setLength(0); // vaciamos el texto para empezar la etiqueta nueva
switch (qName) {
case "Plat":
// empieza un plato nuevo
platActual = new Plat();
break;
case "Grup":
// entramos en el grupo y guardamos su atributo id
dinsGrup = true;
platActual.setGrupId(attributes.getValue("id").trim());
break;
case "Descripcio":
// si es la Descripcio del plato (no la del grupo), leemos sus atributos
if (!dinsGrup) {
platActual.setCodi(attributes.getValue("codi"));
platActual.setPreu(Integer.parseInt(attributes.getValue("preu")));
}
break;
}
}
// SAX llama a este método cuando encuentra TEXTO dentro de una etiqueta
@Override
public void characters(char[] ch, int start, int length) {
text.append(ch, start, length); // lo vamos juntando (puede llegar en trozos)
}
// SAX llama a este método cada vez que se CIERRA una etiqueta, por ejemplo </Plat>
@Override
public void endElement(String uri, String localName, String qName) {
String valor = text.toString().trim(); // texto completo de la etiqueta, sin espacios
switch (qName) {
case "Descripcio":
// según dónde estemos, es el nombre del plato o la descripción del grupo
if (dinsGrup) {
platActual.setGrupDescripcio(valor);
} else {
platActual.setNom(valor);
}
break;
case "Tipus":
// igual: tipo del grupo o tipo del plato
if (dinsGrup) {
platActual.setGrupTipus(valor);
} else {
platActual.setTipus(valor);
}
break;
case "Grup":
// salimos del grupo
dinsGrup = false;
break;
case "Plat":
// el plato ya está completo: lo añadimos a la lista
plats.add(platActual);
break;
}
}
}Main.javaLanza el parser
package exercici6;
import java.io.File;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class Main {
public static void main(String[] args) {
try {
// 1. Creamos el parser SAX
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
// 2. Creamos nuestro handler, que irá montando los platos
MenuHandler handler = new MenuHandler();
// 3. Leemos el fichero: SAX lo recorre y va avisando al handler
parser.parse(new File("MenuXML.xml"), handler);
// 4. Pasamos la lista de platos a un array (lo pide el enunciado)
List<Plat> llista = handler.getPlats();
Plat[] plats = llista.toArray(new Plat[0]);
// 5. Mostramos todos los platos con su toString()
System.out.println("=== Menú ===");
for (Plat p : plats) {
System.out.println(p);
}
} catch (Exception e) {
// si algo falla (no encuentra el fichero, XML mal escrito...) lo mostramos
System.err.println("Error llegint el fitxer: " + e.getMessage());
}
}
}Plat.javaPOJO plano
package exercici6;
// Representa un plato del menú con todos sus datos
public class Plat {
private String codi; // código del plato (A01, A35...)
private int preu; // precio del plato
private String nom; // nombre del plato (Macarrons...)
private String tipus; // tipo de plato (Plat Principal, Postre...)
private String grupId; // id del grupo (01, 02...)
private String grupDescripcio; // descripción del grupo (Aliments farinosos...)
private String grupTipus; // tipo del grupo (Pasta, Fruita...)
// Constructor vacío: creamos el plato y lo vamos rellenando mientras leemos el XML
public Plat() {
}
// Getters y setters
public String getCodi() { return codi; }
public void setCodi(String codi) { this.codi = codi; }
public int getPreu() { return preu; }
public void setPreu(int preu) { this.preu = preu; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
public String getGrupId() { return grupId; }
public void setGrupId(String grupId) { this.grupId = grupId; }
public String getGrupDescripcio() { return grupDescripcio; }
public void setGrupDescripcio(String grupDescripcio) { this.grupDescripcio = grupDescripcio; }
public String getGrupTipus() { return grupTipus; }
public void setGrupTipus(String grupTipus) { this.grupTipus = grupTipus; }
// Muestra el plato con el formato que pide el ejercicio
// [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos)
@Override
public String toString() {
return "[" + codi + "] " + nom
+ " — " + preu + "€"
+ " — " + tipus
+ " — (" + grupTipus + ", " + grupDescripcio + ")";
}
}Resultado
=== Menú === [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos) [A35] Macedònia — 3€ — Postre — (Fruita, Origen vegetal) ...
Parser StAX XML
El mismo resultado que SAX, pero ahora nosotros pedimos el siguiente trozo con reader.next() dentro de un while.
Qué hace
StAX (Streaming API for XML) también lee de arriba abajo sin guardar el documento. La diferencia es que el control lo tiene tu programa: pide un evento, lo trata y pide el siguiente.
Es como leer el libro tú mismo: pasas la página cuando quieres.
Cómo funciona
XMLInputFactory→XMLStreamReader.while (reader.hasNext())yreader.next().- Miras si el evento es
START_ELEMENToEND_ELEMENT. - Lees atributos y texto, y rellenas el
Plat. - Cierras el reader.
Tú mandas (pull). No hay handler: todo cabe en un bucle dentro del Main, y puedes parar cuando quieras con un break. Gasta tan poca memoria como SAX y es más fácil de leer.
SAX vs StAX
| SAX (VI) | StAX (VII) | |
|---|---|---|
| Quién controla | El parser llama a tus métodos | Tú pides con next() |
| Modelo | Push (te empuja) | Pull (tiras tú) |
| Clases | Main + MenuHandler + Plat | Main + Plat |
| Leer el texto | characters() + StringBuilder | getElementText() de una vez |
| Parar a mitad | Difícil | Fácil: break |
| En común | Una sola pasada, sin volver atrás, muy poca memoria, no modifican el XML y los dos necesitan dinsGrup. | |
Código clave
XMLStreamReader reader = XMLInputFactory.newInstance()
.createXMLStreamReader(new FileInputStream("MenuXML.xml"));
while (reader.hasNext()) {
int event = reader.next(); // "dame lo siguiente"
if (event == XMLStreamConstants.START_ELEMENT) {
String nom = reader.getLocalName(); // nombre de la etiqueta
String codi = reader.getAttributeValue(null, "codi"); // null = sin namespace
String text = reader.getElementText(); // texto de dentro
}
}Primero los atributos, luego el texto. getElementText() avanza hasta el cierre de la etiqueta; después ya no puedes leer codi ni preu.
Clases que necesitas
| Clase | Para qué sirve |
|---|---|
| Plat | La misma del ejercicio VI, cambiando solo el package. |
| Main | El bucle StAX completo. |
exercici7/ ├── MenuXML.xml ← en la raíz └── src/exercici7/ ├── Main.java └── Plat.java
Ejercicio completo
Main.javaBucle StAX
package exercici7;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
public class Main {
public static void main(String[] args) {
List<Plat> llista = new ArrayList<>(); // aquí guardamos los platos leídos
Plat platActual = null; // plato que estamos leyendo ahora
boolean dinsGrup = false; // true si estamos dentro de <Grup>
try {
// 1. Creamos el lector StAX a partir del fichero
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("MenuXML.xml"));
// 2. Recorremos el XML: NOSOTROS pedimos el siguiente evento con next()
while (reader.hasNext()) {
int event = reader.next();
// 3. Se ABRE una etiqueta
if (event == XMLStreamConstants.START_ELEMENT) {
String nom = reader.getLocalName(); // nombre de la etiqueta
switch (nom) {
case "Plat":
// empieza un plato nuevo
platActual = new Plat();
break;
case "Grup":
// entramos en el grupo y guardamos su id
dinsGrup = true;
platActual.setGrupId(reader.getAttributeValue(null, "id").trim());
break;
case "Descripcio":
if (dinsGrup) {
// Descripcio del grupo: solo tiene texto
platActual.setGrupDescripcio(reader.getElementText().trim());
} else {
// Descripcio del plato: primero los atributos, luego el texto
platActual.setCodi(reader.getAttributeValue(null, "codi"));
platActual.setPreu(Integer.parseInt(reader.getAttributeValue(null, "preu")));
platActual.setNom(reader.getElementText().trim());
}
break;
case "Tipus":
// tipo del grupo o tipo del plato
if (dinsGrup) {
platActual.setGrupTipus(reader.getElementText().trim());
} else {
platActual.setTipus(reader.getElementText().trim());
}
break;
}
}
// 4. Se CIERRA una etiqueta
else if (event == XMLStreamConstants.END_ELEMENT) {
switch (reader.getLocalName()) {
case "Grup":
// salimos del grupo
dinsGrup = false;
break;
case "Plat":
// el plato está completo: lo añadimos a la lista
llista.add(platActual);
break;
}
}
}
// 5. Cerramos el lector
reader.close();
} catch (Exception e) {
// si algo falla (no encuentra el fichero, XML mal escrito...) lo mostramos
System.err.println("Error llegint el fitxer: " + e.getMessage());
return;
}
// 6. Pasamos la lista a un array (lo pide el enunciado)
Plat[] plats = llista.toArray(new Plat[0]);
// 7. Mostramos todos los platos con su toString()
System.out.println("=== Menú ===");
for (Plat p : plats) {
System.out.println(p);
}
}
}Plat.javaPOJO plano
package exercici7;
// Representa un plato del menú con todos sus datos
public class Plat {
private String codi; // código del plato (A01, A35...)
private int preu; // precio del plato
private String nom; // nombre del plato (Macarrons...)
private String tipus; // tipo de plato (Plat Principal, Postre...)
private String grupId; // id del grupo (01, 02...)
private String grupDescripcio; // descripción del grupo (Aliments farinosos...)
private String grupTipus; // tipo del grupo (Pasta, Fruita...)
// Constructor vacío: creamos el plato y lo vamos rellenando mientras leemos el XML
public Plat() {
}
// Getters y setters
public String getCodi() { return codi; }
public void setCodi(String codi) { this.codi = codi; }
public int getPreu() { return preu; }
public void setPreu(int preu) { this.preu = preu; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
public String getGrupId() { return grupId; }
public void setGrupId(String grupId) { this.grupId = grupId; }
public String getGrupDescripcio() { return grupDescripcio; }
public void setGrupDescripcio(String grupDescripcio) { this.grupDescripcio = grupDescripcio; }
public String getGrupTipus() { return grupTipus; }
public void setGrupTipus(String grupTipus) { this.grupTipus = grupTipus; }
// Muestra el plato con el formato que pide el ejercicio
// [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos)
@Override
public String toString() {
return "[" + codi + "] " + nom
+ " — " + preu + "€"
+ " — " + tipus
+ " — (" + grupTipus + ", " + grupDescripcio + ")";
}
}Resultado
=== Menú === [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos) [A35] Macedònia — 3€ — Postre — (Fruita, Origen vegetal) ...
Parser Jackson JSON
Leemos el menú con ObjectMapper, añadimos dos platos y escribimos el resultado en dos formatos: el original ampliado y uno nuevo, plano y en inglés.
Qué hace
Jackson es la librería de JSON más usada en Java. Ofrece las tres formas: streaming (pull con JsonParser y nextToken()), árbol (JsonNode) y binding (ObjectMapper).
En el ejercicio usamos binding, igual que con Gson.
Cómo funciona
- Añades
jackson-coreyjackson-databindalpom.xml. - Clases del modelo con
@JsonProperty. - Leer:
mapper.readValue(fichero, Menu.class). - Escribir:
mapper.writeValue(fichero, menu). - Otro formato: copias los datos a otras clases y escribes esas.
Es como Gson, pero más completo. Lo nuevo del ejercicio es transformar el formato: se lee con un modelo de clases y se escribe con otro modelo distinto. Jackson usa las anotaciones de cada clase para poner los nombres.
Gson vs Jackson
| Gson (V) | Jackson (VIII) | |
|---|---|---|
| Objeto principal | Gson | ObjectMapper |
| Leer | fromJson(reader, Menu.class) | readValue(file, Menu.class) |
| Escribir | toJson(menu, writer) | writeValue(file, menu) |
| Renombrar clave | @SerializedName("Tipus") | @JsonProperty("Tipus") |
| JSON bonito | setPrettyPrinting() | enable(INDENT_OUTPUT) |
| Orden de campos | — | @JsonPropertyOrder |
Código clave
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Menu menu = mapper.readValue(new File("MenuJSON.json"), Menu.class); // JSON → objetos
mapper.writeValue(new File("MenuJSONAmpliat.json"), menu); // objetos → JSON
// Otro formato: mismo menú, otras clases
MenuTransformat mt = new MenuTransformat(menu); // copia cada Plat a PlatTransformat
mapper.writeValue(new File("MenuJSONTransformat.json"), mt);{
"Menu" : [ {
"Code" : "A01",
"Price" : 10,
"Description" : "Macarrons",
"Type" : "Plat Principal",
"GroupId" : "01",
"GroupDescription" : "Aliments farinosos",
"GroupType" : "Pasta"
} ]
}Errata del enunciado: el ejemplo repite "Type" dos veces, y un JSON no puede repetir una clave. Por eso el tipo del grupo se llama "GroupType".
Clases que necesitas
| Clase | Modelo | Para qué sirve |
|---|---|---|
| Menu | Original | { "Menu": [ … ] } |
| Plat | Original | Descripcio + Tipus + Grup, con toString() |
| Descripcio | Original | codi, preu, text |
| Grup | Original | id, Descripcio, Tipus |
| MenuTransformat | Nuevo | Convierte el menú plato a plato |
| PlatTransformat | Nuevo | Plato plano en inglés: Code, Price… |
| Main | — | Las tres partes del ejercicio |
exercici8/ ├── pom.xml ├── MenuJSON.json ← en la raíz ├── MenuJSONAmpliat.json ← lo crea el programa ├── MenuJSONTransformat.json ← lo crea el programa └── src/main/java/exercici8/ ├── Main.java ├── Menu.java · Plat.java · Descripcio.java · Grup.java └── MenuTransformat.java · PlatTransformat.java
Ejercicio completo
pom.xmlDependencias Jackson
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>exercici8</groupId>
<artifactId>exercici8</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Jackson Core: Streaming API -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.0</version>
</dependency>
<!-- Jackson Databind: mapeo automático JSON <-> objetos Java -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies>
</project>Main.javaLas tres partes
package exercici8;
import java.io.File;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class Main {
public static void main(String[] args) {
// ObjectMapper: la clase de Jackson que convierte JSON <-> objetos Java
ObjectMapper mapper = new ObjectMapper();
// Para que los ficheros que escribimos salgan bien ordenados (con saltos de línea)
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
// ===== PARTE 1: leer el JSON y mostrar los platos =====
// Leemos el fichero y Jackson lo convierte en un objeto Menu (binding)
Menu menu = mapper.readValue(new File("MenuJSON.json"), Menu.class);
// Pasamos la lista de platos a un array (lo pide el enunciado)
Plat[] plats = menu.getPlats().toArray(new Plat[0]);
// Mostramos todos los platos con su toString()
System.out.println("=== Menú ===");
for (Plat p : plats) {
System.out.println(p);
}
// ===== PARTE 2: añadir dos platos y guardar MenuJSONAmpliat.json =====
menu.afegirPlat(new Plat(
new Descripcio("A70", 8, "Amanida verda"),
"Primer Plat",
new Grup("03", "Origen vegetal", "Verdura")));
menu.afegirPlat(new Plat(
new Descripcio("A80", 5, "Crema catalana"),
"Postre",
new Grup("04", "Lactis", "Dolços")));
// Convertimos el menú (objetos) en JSON y lo guardamos
mapper.writeValue(new File("MenuJSONAmpliat.json"), menu);
System.out.println("\nGuardat MenuJSONAmpliat.json");
// ===== PARTE 3: escribir con otro formato (MenuJSONTransformat.json) =====
// Pasamos cada Plat a PlatTransformat (formato plano en inglés)
MenuTransformat menuTransformat = new MenuTransformat(menu);
// Lo guardamos: Jackson usa los nombres de PlatTransformat (Code, Price...)
mapper.writeValue(new File("MenuJSONTransformat.json"), menuTransformat);
System.out.println("Guardat MenuJSONTransformat.json");
} catch (IOException e) {
// si algo falla (no encuentra el fichero, JSON mal escrito...) lo mostramos
System.err.println("Error: " + e.getMessage());
}
}
}Menu.javaRaíz del JSON
package exercici8;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
// Representa todo el menú del JSON: { "Menu": [ ...platos... ] }
public class Menu {
// Lista de platos. "Menu" es el nombre que tiene en el JSON
@JsonProperty("Menu")
private List<Plat> plats = new ArrayList<>();
// Constructor vacío (Jackson lo necesita para crear el objeto)
public Menu() {
}
// Getters y setters (Jackson los usa para leer y escribir)
public List<Plat> getPlats() { return plats; }
public void setPlats(List<Plat> plats) { this.plats = plats; }
// Añade un plato a la lista
public void afegirPlat(Plat plat) { plats.add(plat); }
}Plat.javaUn plato
package exercici8;
import com.fasterxml.jackson.annotation.JsonProperty;
// Representa un plato del menú
public class Plat {
// Descripción del plato (código, precio y nombre)
@JsonProperty("Descripcio")
private Descripcio descripcio;
// Tipo de plato (Plat Principal, Postre...)
@JsonProperty("Tipus")
private String tipus;
// Grupo de alimentos al que pertenece
@JsonProperty("Grup")
private Grup grup;
// Constructor vacío (Jackson lo necesita)
public Plat() {
}
// Constructor para crear platos nuevos
public Plat(Descripcio descripcio, String tipus, Grup grup) {
this.descripcio = descripcio;
this.tipus = tipus;
this.grup = grup;
}
// Getters y setters
public Descripcio getDescripcio() { return descripcio; }
public void setDescripcio(Descripcio descripcio) { this.descripcio = descripcio; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
public Grup getGrup() { return grup; }
public void setGrup(Grup grup) { this.grup = grup; }
// Muestra el plato con el formato que pide el ejercicio
// [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos)
@Override
public String toString() {
return "[" + descripcio.getCodi() + "] " + descripcio.getText()
+ " — " + descripcio.getPreu() + "€"
+ " — " + tipus
+ " — (" + grup.getTipus() + ", " + grup.getDescripcio() + ")";
}
}Descripcio.javacodi, preu, text
package exercici8;
import com.fasterxml.jackson.annotation.JsonProperty;
// Representa la descripción de un plato: { "codi": "A01", "preu": 10, "text": "Macarrons" }
public class Descripcio {
// Código del plato (A01, A35...)
@JsonProperty("codi")
private String codi;
// Precio del plato
@JsonProperty("preu")
private int preu;
// Nombre del plato
@JsonProperty("text")
private String text;
// Constructor vacío (Jackson lo necesita)
public Descripcio() {
}
// Constructor con todos los datos
public Descripcio(String codi, int preu, String text) {
this.codi = codi;
this.preu = preu;
this.text = text;
}
// Getters y setters
public String getCodi() { return codi; }
public void setCodi(String codi) { this.codi = codi; }
public int getPreu() { return preu; }
public void setPreu(int preu) { this.preu = preu; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}Grup.javaid, Descripcio, Tipus
package exercici8;
import com.fasterxml.jackson.annotation.JsonProperty;
// Representa el grupo de alimentos: { "id": "01", "Descripcio": "Aliments farinosos", "Tipus": "Pasta" }
public class Grup {
// Identificador del grupo
@JsonProperty("id")
private String id;
// Descripción del grupo (Aliments farinosos, Lactis...)
@JsonProperty("Descripcio")
private String descripcio;
// Tipo del grupo (Pasta, Fruita...)
@JsonProperty("Tipus")
private String tipus;
// Constructor vacío (Jackson lo necesita)
public Grup() {
}
// Constructor con todos los datos
public Grup(String id, String descripcio, String tipus) {
this.id = id;
this.descripcio = descripcio;
this.tipus = tipus;
}
// Getters y setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getDescripcio() { return descripcio; }
public void setDescripcio(String descripcio) { this.descripcio = descripcio; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
}MenuTransformat.javaFormato nuevo: menú
package exercici8;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
// Menú con el formato NUEVO: { "Menu": [ ...PlatTransformat... ] }
public class MenuTransformat {
@JsonProperty("Menu")
private List<PlatTransformat> plats = new ArrayList<>();
// Constructor vacío (Jackson lo necesita)
public MenuTransformat() {
}
// Convierte el menú normal al formato nuevo, plato a plato
public MenuTransformat(Menu menu) {
for (Plat p : menu.getPlats()) {
plats.add(new PlatTransformat(p));
}
}
public List<PlatTransformat> getPlats() { return plats; }
}PlatTransformat.javaFormato nuevo: plato
package exercici8;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
// Plato con el formato NUEVO (todo plano y en inglés) para MenuJSONTransformat.json
// @JsonPropertyOrder: fija el orden en que se escriben los campos
@JsonPropertyOrder({ "Code", "Price", "Description", "Type", "GroupId", "GroupDescription", "GroupType" })
public class PlatTransformat {
@JsonProperty("Code")
private String code;
@JsonProperty("Price")
private int price;
@JsonProperty("Description")
private String description;
@JsonProperty("Type")
private String type;
@JsonProperty("GroupId")
private String groupId;
@JsonProperty("GroupDescription")
private String groupDescription;
// En el enunciado pone "Type" dos veces, pero un JSON no puede repetir la misma clave,
// así que para el tipo del grupo usamos "GroupType"
@JsonProperty("GroupType")
private String groupType;
// Constructor vacío (Jackson lo necesita)
public PlatTransformat() {
}
// Crea un PlatTransformat copiando los datos de un Plat normal
public PlatTransformat(Plat p) {
this.code = p.getDescripcio().getCodi();
this.price = p.getDescripcio().getPreu();
this.description = p.getDescripcio().getText();
this.type = p.getTipus();
this.groupId = p.getGrup().getId();
this.groupDescription = p.getGrup().getDescripcio();
this.groupType = p.getGrup().getTipus();
}
// Getters (Jackson los usa para escribir el JSON)
public String getCode() { return code; }
public int getPrice() { return price; }
public String getDescription() { return description; }
public String getType() { return type; }
public String getGroupId() { return groupId; }
public String getGroupDescription() { return groupDescription; }
public String getGroupType() { return groupType; }
}Resultado
=== Menú === [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos) [A35] Macedònia — 3€ — Postre — (Fruita, Origen vegetal) [A24] Pastís de formatge — 6€ — Postre — (Dolços, Lactis) [A56] Vedella amb bolets — 12€ — Plat Principal — (Carn, Origen animal) Guardat MenuJSONAmpliat.json Guardat MenuJSONTransformat.json
API JAXB · XML binding
El equivalente de Jackson pero para XML. Anotamos las clases y JAXB convierte MenuXML.xml en objetos y los objetos otra vez en XML.
Qué hace
JAXB (Jakarta XML Binding) no es un parser: es una especificación de binding XML ↔ objetos. Por dentro usa un parser (SAX o StAX).
Desde Java 11 no viene en el JDK: hay que añadir la API y una implementación con Maven.
Cómo funciona
- Anotas las clases: raíz, elementos, atributos y texto.
JAXBContext.newInstance(Menu.class).- Unmarshal = leer: XML → objetos.
- Marshal = escribir: objetos → XML.
- Otro formato: otras clases y otro
JAXBContext.
En XML un dato puede ser etiqueta hija, atributo o texto. Por eso JAXB tiene una anotación para cada caso (@XmlElement, @XmlAttribute, @XmlValue), cosa que en JSON no hace falta.
Las anotaciones
| Anotación | Para qué sirve | Ejemplo en el XML |
|---|---|---|
| @XmlRootElement | La etiqueta raíz | <Menu> |
| @XmlAccessorType(FIELD) | JAXB lee los atributos privados directamente | — |
| @XmlElement | Una etiqueta hija | <Tipus>Pasta</Tipus> |
| @XmlAttribute | Un atributo de la etiqueta | codi="A01" |
| @XmlValue | El texto de dentro | >Macarrons< |
| @XmlType(propOrder) | Orden de las etiquetas al escribir | — |
| @XmlTransient | Excluye un campo | — |
| @XmlElementWrapper | Envuelve una lista en una etiqueta contenedora | <Plats><Plat/>… |
Código clave
JAXBContext context = JAXBContext.newInstance(Menu.class);
// XML → objetos
Menu menu = (Menu) context.createUnmarshaller().unmarshal(new File("MenuXML.xml"));
// objetos → XML
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.marshal(menu, new File("MenuXMLAmpliat.xml"));// <Descripcio codi="A01" preu="10">Macarrons</Descripcio>
@XmlAttribute(name = "codi") private String codi;
@XmlAttribute(name = "preu") private int preu;
@XmlValue private String text;<Menu>
<Grup id="01" descripcio="Aliments farinosos" tipus="Pasta">
<Plat codi="A01" preu="10" tipus="Plat Principal">Macarrons</Plat>
</Grup>
<Grup id="03" descripcio="Origen vegetal" tipus="Fruita">
<Plat codi="A35" preu="3" tipus="Postre">Macedònia</Plat>
<Plat codi="A70" preu="8" tipus="Primer Plat">Amanida verda</Plat>
</Grup>
</Menu>Reglas de las clases
- Constructor vacío público: obligatorio siempre.
- Con
FIELD, los getters y setters son recomendables pero no obligatorios. - Una clase no puede tener
@XmlValuey a la vez@XmlElement.
Detalles del enunciado
- Pide ficheros
.json, pero su contenido es XML: los guardamos como.xml. - Para transformar, se agrupan los platos por
idde grupo. - Si sale un error de módulos, borra
module-info.java.
Clases que necesitas
| Clase | Modelo | Etiqueta que representa |
|---|---|---|
| Menu | Original | <Menu> con lista de <Plat> |
| Plat | Original | <Plat> |
| Descripcio | Original | <Descripcio codi preu>texto |
| Grup | Original | <Grup id> |
| MenuTransformat | Nuevo | <Menu> con lista de <Grup> |
| GrupTransformat | Nuevo | <Grup id descripcio tipus> |
| PlatTransformat | Nuevo | <Plat codi preu tipus>nombre |
exercici9/ ├── pom.xml ├── MenuXML.xml ← en la raíz ├── MenuXMLAmpliat.xml ← lo crea el programa ├── MenuXMLTransformat.xml ← lo crea el programa └── src/main/java/exercici9/ ├── Main.java ├── Menu.java · Plat.java · Descripcio.java · Grup.java └── MenuTransformat.java · GrupTransformat.java · PlatTransformat.java
Ejercicio completo
pom.xmlDependencias JAXB
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>exercici9</groupId>
<artifactId>exercici9</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- API de JAXB (Jakarta XML Binding) -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<!-- Implementación de referencia de JAXB -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.5</version>
</dependency>
</dependencies>
</project>Main.javaLas tres partes
package exercici9;
import java.io.File;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
public class Main {
public static void main(String[] args) {
try {
// ===== PARTE 1: leer el XML y mostrar los platos =====
// JAXBContext: punto de entrada de JAXB, le decimos qué clase es la raíz
JAXBContext context = JAXBContext.newInstance(Menu.class);
// Unmarshaller: convierte XML -> objetos Java
Unmarshaller unmarshaller = context.createUnmarshaller();
Menu menu = (Menu) unmarshaller.unmarshal(new File("MenuXML.xml"));
// Pasamos la lista a un array (lo pide el enunciado)
Plat[] plats = menu.getPlats().toArray(new Plat[0]);
// Mostramos todos los platos con su toString()
System.out.println("=== Menú ===");
for (Plat p : plats) {
System.out.println(p);
}
// ===== PARTE 2: añadir dos platos y guardar el XML ampliado =====
menu.afegirPlat(new Plat(
new Descripcio("A70", 8, "Amanida verda"),
"Primer Plat",
new Grup("03", "Origen vegetal", "Verdura")));
menu.afegirPlat(new Plat(
new Descripcio("A80", 5, "Crema catalana"),
"Postre",
new Grup("04", "Lactis", "Dolços")));
// Marshaller: convierte objetos Java -> XML
Marshaller marshaller = context.createMarshaller();
// Para que el XML salga ordenado, con saltos de línea y sangría
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(menu, new File("MenuXMLAmpliat.xml"));
System.out.println("\nGuardat MenuXMLAmpliat.xml");
// ===== PARTE 3: escribir con otro formato =====
// Convertimos el menú al formato nuevo (platos agrupados por grupo)
MenuTransformat menuTransformat = new MenuTransformat(menu);
// Necesitamos otro JAXBContext, porque la raíz ahora es otra clase
JAXBContext contextT = JAXBContext.newInstance(MenuTransformat.class);
Marshaller marshallerT = contextT.createMarshaller();
marshallerT.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshallerT.marshal(menuTransformat, new File("MenuXMLTransformat.xml"));
System.out.println("Guardat MenuXMLTransformat.xml");
} catch (JAXBException e) {
// si algo falla (no encuentra el fichero, XML mal escrito...) lo mostramos
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
}Menu.javaRaíz <Menu>
package exercici9;
import java.util.ArrayList;
import java.util.List;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
// Etiqueta raíz del XML: <Menu> ... </Menu>
@XmlRootElement(name = "Menu")
// FIELD: JAXB lee y escribe directamente los atributos privados de la clase
@XmlAccessorType(XmlAccessType.FIELD)
public class Menu {
// Cada <Plat> dentro de <Menu> se guarda en esta lista
@XmlElement(name = "Plat")
private List<Plat> plats = new ArrayList<>();
// Constructor vacío (JAXB lo necesita)
public Menu() {
}
public List<Plat> getPlats() { return plats; }
public void setPlats(List<Plat> plats) { this.plats = plats; }
// Añade un plato a la lista
public void afegirPlat(Plat plat) { plats.add(plat); }
}Plat.java<Plat>
package exercici9;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;
// Un <Plat> del menú
@XmlAccessorType(XmlAccessType.FIELD)
// propOrder: orden en que se escriben las etiquetas hijas al generar el XML
@XmlType(propOrder = { "descripcio", "tipus", "grup" })
public class Plat {
// <Descripcio codi="A01" preu="10">Macarrons</Descripcio>
@XmlElement(name = "Descripcio")
private Descripcio descripcio;
// <Tipus>Plat Principal</Tipus>
@XmlElement(name = "Tipus")
private String tipus;
// <Grup id="01"> ... </Grup>
@XmlElement(name = "Grup")
private Grup grup;
// Constructor vacío (JAXB lo necesita)
public Plat() {
}
// Constructor para crear platos nuevos
public Plat(Descripcio descripcio, String tipus, Grup grup) {
this.descripcio = descripcio;
this.tipus = tipus;
this.grup = grup;
}
// Getters y setters
public Descripcio getDescripcio() { return descripcio; }
public void setDescripcio(Descripcio descripcio) { this.descripcio = descripcio; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
public Grup getGrup() { return grup; }
public void setGrup(Grup grup) { this.grup = grup; }
// [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos)
@Override
public String toString() {
return "[" + descripcio.getCodi() + "] " + descripcio.getText()
+ " — " + descripcio.getPreu() + "€"
+ " — " + tipus
+ " — (" + grup.getTipus() + ", " + grup.getDescripcio() + ")";
}
}Descripcio.javaAtributos + texto
package exercici9;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlValue;
// <Descripcio codi="A01" preu="10">Macarrons</Descripcio>
@XmlAccessorType(XmlAccessType.FIELD)
public class Descripcio {
// Atributo codi="A01"
@XmlAttribute(name = "codi")
private String codi;
// Atributo preu="10"
@XmlAttribute(name = "preu")
private int preu;
// El texto que hay DENTRO de la etiqueta: Macarrons
@XmlValue
private String text;
// Constructor vacío (JAXB lo necesita)
public Descripcio() {
}
// Constructor con todos los datos
public Descripcio(String codi, int preu, String text) {
this.codi = codi;
this.preu = preu;
this.text = text;
}
// Getters y setters
public String getCodi() { return codi; }
public void setCodi(String codi) { this.codi = codi; }
public int getPreu() { return preu; }
public void setPreu(int preu) { this.preu = preu; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}Grup.java<Grup id>
package exercici9;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;
// <Grup id="01"><Descripcio>...</Descripcio><Tipus>...</Tipus></Grup>
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "descripcio", "tipus" })
public class Grup {
// Atributo id="01"
@XmlAttribute(name = "id")
private String id;
// Etiqueta hija <Descripcio>Aliments farinosos</Descripcio>
@XmlElement(name = "Descripcio")
private String descripcio;
// Etiqueta hija <Tipus>Pasta</Tipus>
@XmlElement(name = "Tipus")
private String tipus;
// Constructor vacío (JAXB lo necesita)
public Grup() {
}
// Constructor con todos los datos
public Grup(String id, String descripcio, String tipus) {
this.id = id;
this.descripcio = descripcio;
this.tipus = tipus;
}
// Getters y setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getDescripcio() { return descripcio; }
public void setDescripcio(String descripcio) { this.descripcio = descripcio; }
public String getTipus() { return tipus; }
public void setTipus(String tipus) { this.tipus = tipus; }
}MenuTransformat.javaFormato nuevo: menú
package exercici9;
import java.util.ArrayList;
import java.util.List;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
// Formato nuevo: <Menu> <Grup ...> <Plat ...>...</Plat> </Grup> </Menu>
@XmlRootElement(name = "Menu")
@XmlAccessorType(XmlAccessType.FIELD)
public class MenuTransformat {
// Ahora el menú contiene GRUPOS (y cada grupo contiene sus platos)
@XmlElement(name = "Grup")
private List<GrupTransformat> grups = new ArrayList<>();
// Constructor vacío (JAXB lo necesita)
public MenuTransformat() {
}
// Convierte el menú normal al formato nuevo, agrupando los platos por grupo
public MenuTransformat(Menu menu) {
for (Plat p : menu.getPlats()) {
// buscamos si ya hemos creado este grupo
GrupTransformat grup = buscarGrup(p.getGrup().getId());
// si no existe, lo creamos y lo añadimos al menú
if (grup == null) {
grup = new GrupTransformat(p.getGrup());
grups.add(grup);
}
// metemos el plato dentro de su grupo
grup.afegirPlat(new PlatTransformat(p));
}
}
// Devuelve el grupo con ese id, o null si todavía no existe
private GrupTransformat buscarGrup(String id) {
for (GrupTransformat g : grups) {
if (g.getId().equals(id)) {
return g;
}
}
return null;
}
}GrupTransformat.javaFormato nuevo: grupo
package exercici9;
import java.util.ArrayList;
import java.util.List;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
// Formato nuevo: <Grup id="01" descripcio="Aliments farinosos" tipus="Pasta"> ...platos... </Grup>
@XmlAccessorType(XmlAccessType.FIELD)
public class GrupTransformat {
@XmlAttribute(name = "id")
private String id;
@XmlAttribute(name = "descripcio")
private String descripcio;
@XmlAttribute(name = "tipus")
private String tipus;
// Los platos que pertenecen a este grupo
@XmlElement(name = "Plat")
private List<PlatTransformat> plats = new ArrayList<>();
// Constructor vacío (JAXB lo necesita)
public GrupTransformat() {
}
// Crea el grupo copiando los datos de un Grup normal
public GrupTransformat(Grup g) {
this.id = g.getId();
this.descripcio = g.getDescripcio();
this.tipus = g.getTipus();
}
public String getId() { return id; }
// Añade un plato dentro de este grupo
public void afegirPlat(PlatTransformat p) { plats.add(p); }
}PlatTransformat.javaFormato nuevo: plato
package exercici9;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlValue;
// Formato nuevo: <Plat codi="A01" preu="10" tipus="Plat Principal">Macarrons</Plat>
@XmlAccessorType(XmlAccessType.FIELD)
public class PlatTransformat {
@XmlAttribute(name = "codi")
private String codi;
@XmlAttribute(name = "preu")
private int preu;
@XmlAttribute(name = "tipus")
private String tipus;
// Texto dentro de la etiqueta: el nombre del plato
@XmlValue
private String nom;
// Constructor vacío (JAXB lo necesita)
public PlatTransformat() {
}
// Crea el plato nuevo copiando los datos de un Plat normal
public PlatTransformat(Plat p) {
this.codi = p.getDescripcio().getCodi();
this.preu = p.getDescripcio().getPreu();
this.tipus = p.getTipus();
this.nom = p.getDescripcio().getText();
}
}Resultado
=== Menú === [A01] Macarrons — 10€ — Plat Principal — (Pasta, Aliments farinosos) ... Guardat MenuXMLAmpliat.xml Guardat MenuXMLTransformat.xml
INS Marianao · DAM 2 · M0486 Accés a Dades. Código con comentarios en castellano, listo para Eclipse.