Współautor części materiału : Tomasz Ziętkiewicz
Uwaga! Kod do tych zajęć znajduje się na gałęzi JSONandXMLStart w repozytorium https://github.com/WitMar/PRO2023.git . Kod końcowy w gałęzi JSONandXMLEnd.
Jeżeli nie widzisz odpowiednich gałęzi na GitHubie wykonaj Ctr+T, a jak to nie pomoże to wybierz z menu Git->Fetch.
JSON (JavaScript Object Notation) http://www.json.org/ to lekki, tekstowy format wymiany danych.
Jest oparty na podzbiorze języka JavaScript.
Powszechnie wykorzystywany do przechowywania i przekazywania ustrukturyzowanych danych w postaci tekstowej.
Właściwości JSON-a:
czytelny dla człowieka
szeroko rozpowszechniony - biblioteki dla każdego języka (lista na http://www.json.org)
{
"artist": "Pink Floyd",
"title": "Dark Side of the moon",
"year": 1973,
"tracks": [
{
"track#": 1,
"title": "Speak to Me/Breathe",
"length": "3:57",
"music": ["Mason"]
},
{
"track#": 2,
"title": "On the run",
"length": "3:50",
"music": ["Waters", "Gilmour"]
}
]
}
Przykład z życia: API rowerów miejskich
Dwie struktury danych:
object (obiekt, słownik, mapa) zbiór par klucz-wartość
{
"title": "Dark Side of the Moon",
"year": 1973,
"tracks#": 9
}
uporządkowany zbiór wartości
{
"name":"John",
"age":30,
"cars":[ "Ford", "BMW", "Fiat" ]
}
Siedem typów wartości:
Do pliku JSON nie możemy dodawać komentarzy
XML (Extensible Markup Language) - język znaczników (markup language), który podobnie jak JSON umożliwia serializację i wymianę strukturalnych danych w postaci tekstowej.
W dokumencie XML możemy wydzielić zawartość (content) i * znaczniki (markup).
Znaczniki znajdują się między parami znaków "<" i ">" lub "&" i ";".
Treść dokumentu to wszystkie znaki, które nie są znacznikami.
tagi początku elementu:
<album>
tagi końca elementu:
</album>
tagi puste (bez zawartości):
<album />
Element rozpoczna się tagiem początku, kończy tagiem końca elementu, albo jest pustym tagiem.
Pomiędzy tagami znajduje się zawartość elementu, którym może być albo zwykły tekst, albo zagnieżdżone elementy.
Tagi początkowy i pusty mogą zawierać atrybuty, czyli pary klucz-wartość.
Klucz podajemy jako text bez cudzysłowów, wartości zawsze w cudzysłowie.
<track number="3" title="Time" length="3:57">
Ticking away the moments that make up a dull day
You fritter and waste the hours in an offhand way
Kicking around on a piece of ground in your home town
Waiting for someone or something to show you the way
</track>
Komentarze znajdują się między znacznikami "<!--" i "-->".
<?xml version="1.0" encoding="UTF-8"?>
<album title="Dark Side of the Moon" year="1973">
<track number="1" title="Speak to Me/Breathe">
Breathe, breathe in the air
Don't be afraid to care
Leave but don't leave me
Look around and choose your own ground
For long you live and high you fly
Smiles you'll give and tears you'll cry
And all you touch and all you see
Is all your life will ever be
</track>
<track number="2" title="On the run" />
<track number="3" title="Time" length="3:57">
Ticking away the moments that make up a dull day
You fritter and waste the hours in an offhand way
Kicking around on a piece of ground in your home town
Waiting for someone or something to show you the way
</track>
</album>
Życiowy przykład: API rowerów miejskich, XML
Serializacja - proces polegający na przekształceniu struktur danych albo stanu obiektu do sekwencyjnej formy, która umożliwa zapisanie lub przesłanie tych danych i potencjalnie odtworzenie struktur danych lub obiektów w późniejszym czasie/przez inny proces/komputer (deserializację).
Na przykład, serializacja może polegać na zapisie do pliku w formacie JSON obiektów wygenerowanych przez nasz program, w celu późniejszego wczytania tych obiektów z powrotem do programu w celu kontynuowania obliczeń.
JSON i XML są przykładami formatów dobrze nadających się do serializacji danych w sposób czytelny dla człowieka.
Można również serializować dane w postaci binarnej, niezrozumiałej dla człowieka.
Jackson - zestaw narzędzi do przetwarzania danych dla Javy ("suite of data-processing tools for Java").
Głównym komponentem jest generator/parser JSON, pozwalający m.in. na deserializację/serializację do/z JSON z/do Javy.
Posiada liczne moduły dodające obsługę innych formatów danych, m.in. XML, YAML czy CSV.
Strona domowa projektu nie działa, ale projekt jest aktywnie rozwijany na GitHub.
Zarchiwizowana wersja strony domowej.
Zadanie 0: Uruchom klasę JacksonSerialization
Uruchom kod, zobacz jaki błąd widzisz. Dodaj do katalou resources plik employee.json i wklej do niego zawartość pliku result.json, czy błąd zniknął? Uważaj na to jak jest opisany błąd programu, gdyż może on czasem być mylący.
Sprawdź jaka wersja biblioteki Jackson jest zainmportowana do pom.xml
Wskazówka: Odpowiedni wpis znajdzie w dokumentacji w repozytorium maven.
Zadanie 1: Serializacja do JSON przez Jackson
Zobacz jak tworzeni są pracownicy w kodzie oraz jak są serializowani oraz co jest wynikiem programu. Każdy z pracowników ma przypisany adres.
Zobacz jak za pomocą om.fasterxml.jackson.databind.ObjectMapper zapisywany (serializowany) jest obiekt.
Przykłady:
Zadanie 2: Deserializacja z JSON
Odtwórz obiekt przełożonego z utworzonego w zadaniu 1. pliku JSON. Zmień przełożonemu wynagrodzenie najpierw w pliku, sprawdź za pomocą debuggera, że zmiana zadziałąła. Uruchom kod zmieniający wynagrodzenie i zobacz wynik serializacji do pliku json.
Zadanie 3: Annotacje
W języku Java dla zapisu nazwy pól klasy przyjmuje się konwencję notacji lowerCamelCase.
W JSON nie ma przyjętego standardu notacji (dyskusja na StackOverflow).
Domyślnie pola w JSONie wygenerowanym przez Jackson/Gson mają takie same nazwy jak pola w klasie, którą serializujemy. Dodaj do modelowanych klas annotacje zmieniającą nazwę salary na "wynagrodzenie" oraz adnotacje do ignorowania pola "pesel" przy serializacji.
Wskazówka: Skorzystaj z dokumentacji.
Zobacz w jakiej kolejności serializowane są pola oraz odpowiedz na pytanie czemu wystąpił błąd deserializacji. Napraw błąd.
Zadanie 4: Deserializacja typów generycznych
Spróbuj serializować listę pracowników (w postaci ArrayList) zobacz jak wygląda plik json zawierający listę kilku pracowników. Wczytaj tę listę do kolekcji ArrayList<Employee>.
Wskazówka: Skorzystaj z 3 minute tutorial.
Jackson posiada moduł rozszerzający go o obsługę formatu XML. Aby użyć XML zamiast JSON wystarczy zmienić "ObjectMapper" na XmlMapper":
ObjectMapper xmlMapper = new XmlMapper();
Więcej szczegółów:
http://www.baeldung.com/jackson-xml-serialization-and-deserialization
Zadanie 5: XML
Korzystając z istniejącej klasy JacksonSerialization zmodyfikuj ją, albo stwórz nową klasę tak, żeby umożliwić serializację / deserializację do/z formatu XML. Dodaj do katalogu main/resources pliki xml odpowiadające istniejącym już plikom json.
Wskazówka: Nie musisz tworzyć zawartości plików xml samodzielnie, możesz wygenerować je za pomocą odpowiednich metod.
Wskazówka: Pamiętaj żeby dodać bibliotekę do pom.xml. Odpowiedni wpis znajdziesz w repozytorium GitHub modułu XML.
Zadanie 6: Joda Time
Dodaj do klasy Employee pole
DateTime birthDate
zawierające datę urodzenia pracownika.
Zobacz jak teraz serializuje się plik.
StackOverflow powie Ci, że brakuje Ci adnotacji względem tego jak formatowana powinna być data. Dodaj nad polem daty adnotacje
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSSZ")
Spróbuj dokonać serializacji a następnie deserializacji obiektu tak zmodyfikowanej klasy.
Wskazówka: Możesz potrzebować modułu jackson-datatype-joda.
Żeby wszystko zadziałało potrzebujesz zarejestrować w mapperze moduł Joda.
Zadanie 7: rekurencyjne odwołania
Zauważ, że w JSONie wypisywane są całe obiekty wraz z zależnościami. Co stałoby się gdyby pracownik X miał podwładnego Y którego podwładnym byłby znów X? Otrzymalibyśmy nieskończoną rekurencję i błąd serializacji. Żeby tego uniknąć możemy zastosować adnotację:
Odkomentuj 61 linie w ModeObjectCreator.java i dodaj:
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="refId", scope=Employee.class) public class Employee { ... }
Która spowoduje, że obiekty wypisywane będą tylko raz, a przy wielokrotnych odwołaniach zostanie zastosowane Id jako referencja.
Więcej na ten temat:
http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
Generate classes from Json / Json Schema :
Generate classes form XML schema using Linux command
Uncomment classes ClassLoadinUtil,java and Unmarshaller.java
xjc
First create XSD schema file classes.xsd:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="www.wmi.amu.edu.pl" xmlns:tns="www.wmi.amu.edu.pl" elementFormDefault="qualified"> <element name="employee" type="tns:employee"/> <complexType name="employee"> <sequence> <element name="name" type="string" minOccurs="0"/> <element name="salary" type="double"/> <element name="designation" type="string" minOccurs="0"/> <element name="address" type="tns:address" minOccurs="0"/> </sequence> <attribute name="id" type="int" use="required"/> </complexType> <complexType name="address"> <sequence> <element name="city" type="string" minOccurs="0"/> <element name="line1" type="string" minOccurs="0"/> <element name="line2" type="string" minOccurs="0"/> <element name="state" type="string" minOccurs="0"/> <element name="zipcode" type="long"/> </sequence> </complexType> </schema>
Go into the directory where schema is saved and run command
mkdir output xjc -d output -p model2 classes.xsd
The clasess should be generated to directory output with package name model2.
Create xml file classes.xml :
<?xml version="1.0" encoding="UTF-8"?>
<tns:employee id="1" xmlns:tns="www.wmi.amu.edu.pl">
<tns:name>Marcin Witkowski</tns:name>
<tns:salary>100</tns:salary>
<tns:designation>Poznan</tns:designation>
<tns:address>
<tns:city>Poznan</tns:city>
<tns:line1>Uniwersytetu 78</tns:line1>
<tns:zipcode>60606</tns:zipcode>
</tns:address>
</tns:employee>
Check how Intellij helps you with giving hints on how to create the XML.
Validate your XML
public boolean validate(String xmlFile, StringBuilder error) {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
Resource resource = new ClassPathResource("classes.xsd");
InputStream input = resource.getInputStream();
File file = resource.getFile();
Schema schema = schemaFactory.newSchema(file);
Validator validator = schema.newValidator();
validator.validate(new StreamSource(new StringReader(xmlFile)));
return true;
} catch (SAXException | IOException e) {
error.append(e.getMessage());
return false;
}
}
Serialize the XML with
File file = new File("classes.xml");
try (InputStream inputStream = new FileInputStream(file)) {
model2.Employee a = new Unmarshaller().unmarshallConfiguration(inputStream);
System.out.println(a.getName() + " " + a.getAddress().getCity());
}