En aquesta unitat anem a estudiar algunes llibreries que ens permeten accelerar el procés de manipular els tipus de fitxers estudiats prèviament. Aquestes biblioteques faran servir classes serialitzadores i/o anotacions a les nostres classes per a definir la estrucutra dels fitxers a partir de les classes.
1. Jackson
1.1. Què és Jackson?
Ja hem vist que en algun moment hauràs de gestionar dades en format JSON (JavaScript Object Notation). Jackson és una llibreria de Java que simplifica enormement aquest procés, convertint-se en una eina pràcticament imprescindible en l'ecosistema Java modern.
Jackson fa una cosa molt concreta i la fa excepcionalment bé: la seriació i deseriatzació de dades entre objectes Java i el seu equivalent en format JSON.
Seriació (o Marshalling): Converteix un objecte Java (una instància d'una classe) en una cadena de text amb format JSON.
Deseriatzació (o Unmarshalling): El procés invers, converteix una cadena de text JSON en un objecte Java equivalent.
1.2. Què necessite?
Necessitem afegir la següent denpendència al nostre porjecte:
<dependencies><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.19.0</version><!-- o la versió més actual --></dependency></dependencies>
Jackson farà servir principalment les següents classes:
ObjectMapper: És la classe principal de Jackson i s'encarrega de la conversió entre objectes Java i JSON:
writeValue(): Mètode per convertir un objecte Java en JSON.
readValue(): Mètode per convertir JSON en un objecte Java.
JsonNode: Aquesta classe permet treballar amb arbres JSON en format node i és útil quan no coneixes l'estructura exacta del JSON.
1.3. Exemple
Si partim d'una classe o conjunt de classes sense cap modificació a la classe, ens farà un objecte JSNO com segueix
// Creem l'ObjectMapperObjectMapperobjectMapper=newObjectMapper();// Convertim l'objecte a JSONStringjson=null;try{// string a pelo, sin formatojson=objectMapper.writeValueAsString(llibre);System.out.println(json);// així millorjson=objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(llibre);System.out.println(json);}// falta la captura d'excepcions
Llibrellibre1=newLlibre(1,"La casa de los espíritus","Realista");Llibrellibre2=newLlibre(2,"Cien años de soledad","Realista");Llibrellibre3=newLlibre(3,"El amor en los tiempos del cólera","Romàntic");Llibrellibre4=newLlibre(4,"La sombra del viento","Misteri");Llibrellibre5=newLlibre(5,"El juego del ángel","Misteri");Llibrellibre6=newLlibre(6,"Kafka en la orilla","Filosòfic");Llibrellibre7=newLlibre(7,"Norwegian Wood","Drama");Llibrellibre8=newLlibre(8,"1Q84","Ciència ficció");// Creem 5 autorsAutorautor1=newAutor(1,"Isabel Allende","Xile",Arrays.asList(llibre1,llibre2));Autorautor2=newAutor(2,"Gabriel García Márquez","Colòmbia",Arrays.asList(llibre3));Autorautor3=newAutor(3,"Carlos Ruiz Zafón","Espanya",Arrays.asList(llibre4,llibre5));Autorautor4=newAutor(4,"Haruki Murakami","Japó",Arrays.asList(llibre6,llibre7,llibre8));Autorautor5=newAutor(5,"Elena Ferrante","Itàlia",Arrays.asList());// Imprimim els autorsautors=Arrays.asList(autor1,autor2,autor3,autor4,autor5);llibres=Arrays.asList(llibre1,llibre2,llibre3,llibre4,llibre5,llibre6,llibre7,llibre8);
[{"id":1,"titol":"La casa de los espíritus","genere":"Realista"},{"id":2,"titol":"Cien años de soledad","genere":"Realista"},{"id":3,"titol":"El amor en los tiempos del cólera","genere":"Romàntic"},{"id":4,"titol":"La sombra del viento","genere":"Misteri"},{"id":5,"titol":"El juego del ángel","genere":"Misteri"},{"id":6,"titol":"Kafka en la orilla","genere":"Filosòfic"},{"id":7,"titol":"Norwegian Wood","genere":"Drama"},{"id":8,"titol":"1Q84","genere":"Ciència ficció"}]
[{"id":1,"nom":"Isabel Allende","nacionalitat":"Xile","llibres":[{"id":1,"titol":"La casa de los espíritus","genere":"Realista"},{"id":2,"titol":"Cien años de soledad","genere":"Realista"}]},{"id":2,"nom":"Gabriel García Márquez","nacionalitat":"Colòmbia","llibres":[{"id":3,"titol":"El amor en los tiempos del cólera","genere":"Romàntic"}]},{"id":3,"nom":"Carlos Ruiz Zafón","nacionalitat":"Espanya","llibres":[{"id":4,"titol":"La sombra del viento","genere":"Misteri"},{"id":5,"titol":"El juego del ángel","genere":"Misteri"}]},{"id":4,"nom":"Haruki Murakami","nacionalitat":"Japó","llibres":[{"id":6,"titol":"Kafka en la orilla","genere":"Filosòfic"},{"id":7,"titol":"Norwegian Wood","genere":"Drama"},{"id":8,"titol":"1Q84","genere":"Ciència ficció"}]},{"id":5,"nom":"Elena Ferrante","nacionalitat":"Itàlia","llibres":[]}]
1.4. Modificant algunes propietats del json
Jackson ofereix un ampli conjunt d'anotacions per a personalitzar el procés de conversió sense haver de modificar la lògica del codi. Algunes bàsiques son:
@JsonProperty pots canviar el nom d'un camp en el JSON
publicclassLlibre2{// Canviem "id" per "isbn"@JsonProperty("isbn")// Opcional, ja que el nom de l'atribut coincideix amb el nom del JSONprivateintisbn;@JsonProperty("titol_llibre")// Canvia el nom de "titol" a "titol_llibre" al JSONprivateStringtitol;privateStringgenere;@JsonIgnore// Omiteix el camp "notaInterna" a l'hora de convertir a JSONprivateStringnotaInterna;@JsonFormat(pattern="dd-MM-yyyy")// Personalitza el format de la dataprivateDatedataPublicacio;// Constructor, getters i settes}
1.5. Avancem continugt
Spring, el workbench que estudiarem a l'unitat 6, fa servir internament Jackson per a seriar els objectes i retornar-los mitjançant la seua API. De vegades ens podrem trobar problemes quan tenim objectes doblement enllaçats, com per exemple un Jugador que ha pertany un Equip on dins dels Equips tenim guardat els seus Jugador.
Aquest problema no és nou, ja que por apareixer també al generar el propi toString i l'aparicicó del famós missatge StackOverflowException degut a la recursió infinita d'anar d'un objecte a altre.
Per a evitar-ho amb la serialització amb JSON, disposem de @JsonManagedReference i @JsonBackReference.
@JsonManagedReference: Es col·loca a la propietat que s'ha de serialitzar normalment (el "pare" o el costat "cap avant" de la relació). En aquesta classe Sí volem que aparega les propietats. Al nostre exemple, seria la llista de Jugadors dins de la classe Equip.
@JsonBackReference: Es col·loca a la propietat que causa el bucle i que no es tornarà a serialitzar (el "fill" o el costat "cap enrere"). En aquest cas, seria la propietat Equip dins de la classe Jugador. Això el que provoca també és que no es mostre l'equip al convertir el jugador
publicclassEquip{publiclongid;publicStringnom;@JsonManagedReferencepublicList<Jugador>jugadors;// Constructors Getters i Setters (omesos per brevetat)}
publicvoidtestReferencies(){// 1. Creem l'equipEquipvalenciaCF=newEquip(1,"València CF");// 2. Creem els jugadorsJugadorjugador1=newJugador(10,"Pepelu",18);Jugadorjugador2=newJugador(20,"Hugo Duro",9);Jugadorjugador3=newJugador(25,"Giorgi Mamardashvili",1);// 3. Afegim els jugadors a l'equip (establint la relació bidireccional)valenciaCF.afegirJugador(jugador1);valenciaCF.afegirJugador(jugador2);valenciaCF.afegirJugador(jugador3);// 4. Mostrem l'equip (i els seus jugador)sampleObjectToJson(valenciaCF);// 5. Mostrem un jugador (però no l'equip)sampleObjectToJson(jugador1);}
Prova a buscar una solució més completa amb @JsonIdentityInfo
2. JAXB
Per processar XML, saps que amb DOM, has de recórrer arbres de nodes (NodeList, Element, Attr). JAXB (Java Architecture for XML Binding) representa un canvi de paradigma. En lloc de veure l'XML com un arbre de nodes, JAXB el tracta com una font de dades per a objectes Java. La seva funció principal és actuar com un pont automàtic entre el món dels documents XML i el món dels teus objectes de domini (POJOs - Plain Old Java Objects).
- Unmarshalling: És el procés de llegir un document XML i convertir-lo automàticament en una estructura d'objectes Java. JAXB llegeix l'XML, instancia les classes corresponents i assigna els valors als seus camps.
- Marshalling: És el procés invers. Agafa un objecte Java i el converteix en la seva representació XML equivalent.
De manera molt similar a com la llibreria Jackson ho fa amb JSON, JAXB utilitza anotacions per mapejar les classes i els camps de Java amb els elements i atributs de l'XML.
Primerament caldrà afegir la dependència de JABX al POM (en projectes maven)
@XmlRootElement → És l'anotació més important. Indica que aquesta classe pot ser l'element arrel d'un document XML. Sense ella, JAXB no sabria per on començar a l'hora de fer marshalling d'un objecte.
@XmlAccessorType → Defineix quins camps de la classe s'han d'incloure en el procés de binding per defecte. L'opció més comuna és XmlAccessType.FIELD
@XmlType → Permet definir l'ordre en què apareixeran els elements dins de l'XML generat, la qual cosa és molt útil per crear documents amb una estructura predefinida i consistent. @XmlType(propOrder = { "nom", "dorsal", "demarcacio" })
2.2. Anotacions a nivell de atribut
@XmlElement → indica que és un element XML
@XmlAttribute → indica que serà com un atribut
@XmlElementWrapper → indica que aquesta llista és una colecció d'elements
@XmlTransient → indica que aquest atribut no es processa (com el JSONIgnore)
2.3. I el problema de la recursió
La solució per a la recursivitat en JAXB es fa normalment amb les anotacions @XmlID i @XmlIDREF. Vegem l'exemple amb Jugador i Equip
@XmlID: Es posa en un camp que actua com a identificador únic (i ha de ser de tipus String). Aquest serà l'id del nostre Equip.
@XmlIDREF: Es posa en el camp que fa la referència de tornada (el camp equip dins de Jugador). En lloc d'incrustar tot l'objecte, JAXB només escriurà el valor de l'identificador (@XmlID) de l'objecte referenciat.
importcom.fasterxml.jackson.annotation.JsonBackReference;importjakarta.xml.bind.annotation.*;@XmlAccessorType(XmlAccessType.FIELD)@XmlType(propOrder={"id","nom","equip"})// Defineix l'ordre dels elements internspublicclassJugador{@XmlElementpubliclongid;@XmlElementpublicStringnom;@XmlAttributepublicintdorsal;@JsonBackReference()@XmlIDREF// <-- Aquesta és la clau per evitar la recursivitat!publicEquipequip;// ConstructorspublicJugador(){}publicJugador(longid,Stringnom,intdorsal){this.id=id;this.nom=nom;this.dorsal=dorsal;}// Getters i SetterspublicvoidsetEquip(Equipequip){this.equip=equip;}// La resta de getters i setters s'ometen per brevetat}
importcom.fasterxml.jackson.annotation.JsonManagedReference;importjakarta.xml.bind.annotation.*;importjava.util.ArrayList;importjava.util.List;@XmlRootElement(name="equip")@XmlAccessorType(XmlAccessType.FIELD)// Processa els camps directamentpublicclassEquip{@XmlAttribute// L'ID de l'equip serà un atribut@XmlID// Aquest és l'identificador únic per a JAXBpublicStringid;// Canviat a String per complir amb @XmlID que requereix String@XmlElementpublicStringnom;@JsonManagedReference@XmlElementWrapper(name="elsJjugadors")// Etiqueta que envoltarà la llista@XmlElement(name="jugador")// Nom per a cada element de la llistapublicList<Jugador>jugadors;// ConstructorspublicEquip(){this.jugadors=newArrayList<>();}publicEquip(Stringid,Stringnom){this.id=id;this.nom=nom;this.jugadors=newArrayList<>();}// Mètode d'ajuda per afegir jugadors i establir la relaciópublicvoidafegirJugador(Jugadorjugador){this.jugadors.add(jugador);jugador.setEquip(this);}// Getters i Setters (omesos per brevetat)}
publicvoidtestXML(StringFilename){try{// 1. Creem l'equip i els jugadorsEquipvalenciaCF=newEquip("Valencia2025","València CF");Jugadorj1=newJugador(10,"Pepelu",18);Jugadorj2=newJugador(20,"Hugo Duro",9);// 2. Vinculem els jugadors a l'equipvalenciaCF.afegirJugador(j1);valenciaCF.afegirJugador(j2);// 3. Creem el context JAXB per a les classes que utilitzaremJAXBContextcontext=JAXBContext.newInstance(Equip.class);// 4. Creem el MarshallerMarshallermarshaller=context.createMarshaller();// Configurem la propietat per a una sortida XML formatada (indentada)marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);// 5. Convertim l'objecte a XML i ho mostrem per la sortida estàndardSystem.out.println("--- Resultat XML Generat ---");marshaller.marshal(valenciaCF,System.out);// 6. També podem guardar el XML en un fitxerif(Filename!=null&&!Filename.isEmpty()){marshaller.marshal(valenciaCF,newjava.io.File(Filename));}}catch(JAXBExceptione){thrownewRuntimeException(e);}}
titol,director,any,generes
Inception,Christopher Nolan,2010,Sci-Fi
The Dark Knight,Christopher Nolan,2008,Action
Interstellar,Christopher Nolan,2014,Sci-Fi
,Bong Joon-ho,2019,Thriller
Joker,Todd Phillips,2019,Crime
Avengers: Endgame,Anthony Russo,2019,Action
1917,Sam Mendes,2019,War
Tenet,,2020,Sci-Fi
Soul,Pete Docter,2020,Animation
Knives Out,Rian Johnson,2019,Mystery
Jojo Rabbit,Taika Waititi,2019,Comedy
Ford v Ferrari,James Mangold,2019,Drama
The Irishman,Martin Scorsese,,Crime
Once Upon a Time in Hollywood,Quentin Tarantino,2019,Drama
Little Women,Greta Gerwig,2019,Drama
Marriage Story,Noah Baumbach,2019,Drama
La La Land,Damien Chazelle,2016,Musical
Dunkirk,Christopher Nolan,2017,War
Blade Runner 2049,Denis Villeneuve,2017,Sci-Fi
Arrival,Denis Villeneuve,2016,Sci-Fi
publicvoidtestCommonCSV(){StringcsvFile="peliculas.csv";// Ruta del CSVList<Pelicula>pelicules=newArrayList<>();intnumRecords=0;try{FileReaderreader=newFileReader(csvFile);CSVFormatformat=CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build();CSVParsercsvParser=newCSVParser(reader,format);for(CSVRecordrecord:csvParser){numRecords++;Stringtitol=record.get("titol");Stringdirector=record.get("director");StringanyStr=record.get("any");Stringgenere=record.get("generes");// Comprovem si alguna dada és nula o buidaif(titol==null||titol.isEmpty()||director==null||director.isEmpty()||anyStr==null||anyStr.isEmpty()||genere==null||genere.isEmpty()){continue;// Saltar aquesta pel·lícula}intany=Integer.parseInt(anyStr);Peliculapelicula=newPelicula(titol,director,any,genere);pelicules.add(pelicula);}}catch(FileNotFoundExceptionex){thrownewRuntimeException(ex);}catch(IOExceptionex){thrownewRuntimeException(ex);}System.out.print(Colors.green+"S'han llegit "+numRecords+" registres"+Colors.reset);System.out.println(Colors.yellow+". Però finalment llegides "+pelicules.size()+Colors.reset);// Mostrem totes les pel·lícules carregadesfor(Peliculap:pelicules){System.out.println(p);}}