Autor: Tomasz Ziętkiewicz
Uwaga! cały kod do Zadania 2 włącznie znajduje się na gałęzi serialization w repozytorium https://github.com/WitMar/PRA . Kod końcowy w gałęzi serializationFinal.
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.
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) 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:
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 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.
GSON to biblioteka języka Java służąca do (de)serializacji JSON.
stworzona i rozwijana przez Google
udostępniona na otwartej licencji Apache License 2.0.
łatwa w użyciu
nie wymaga (ale umożliwia) dodawania adnotacji do serializowanych klas
może być użyta na kodzie, którego nie możemy modyfikować
// Serialization
Gson gson = new Gson();
gson.toJson(1); // ==> 1
gson.toJson("abcd"); // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values); // ==> [1]
// Deserialization
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
// Serialization
gson.toJson(ints); // ==> [1,2,3,4,5]
gson.toJson(strings); // ==> ["abc", "def", "ghi"]
// Deserialization
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
// ==> ints2 will be same as ints
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}
transient - słowo kluczowe Java, oznaczające, że dane pole nie ma podlegać serializacji
prywatne pola też są serializowane/deserializowane
pola mające wartość null nie są serializowane
jeśli jakiegoś pola brakuje w jsonie, to przy deserializacji przyjmuje ono wartość domyślną (tak, jakbyśmy nie zainicjowali zmiennej: obiekty null, numeryczne 0 a boolowiskie false)
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);
// Serialization
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
// Deserialization
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 is same as ints
Podczas deserializacji generycznych typów należy uzyć specjalnej klasy TypeToken w celu uzyskania typu wynikowej kolekcji, która zostanie stworzona w wyniku deserializacji ( więcej na ten temat tutaj ).
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);
System.out.println(jsonOutput);
Dodaj Gson do pom.xml
<dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.2</version> </dependency> </dependencies>
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.
Dodaj bibliotekę Jackson do pom.xml
Wskazówka
Odpowiedni wpis znajdzie w dokumentacji w repozytorium GitHub modułu.
Stwórz dwóch pracowników (obiekty klasy Employee). Niech jeden pracownik będzie przełożonym a drugi jego podwładnym.
Każdemu z pracowników przypisz adres.
Za pomocą om.fasterxml.jackson.databind.ObjectMapper zapisz (dokonaj serializacji) obiektu przełożonego do pliku json.
Przykłady:
Odtwórz obiekt przełożonego z utworzonego w zadaniu 1. pliku JSON. Zmień przełożonemu wynagrodzenie i zapisz (dokonaj serializacji) do pliku json.
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 "pieniadz" oraz adnotacje do ignorowania pola "pesel" przy serializacji.
Wskazówka
Skorzystaj z dokumentacji.
Stwórz 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();
http://www.baeldung.com/jackson-xml-serialization-and-deserialization
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.
Dodaj do klasy Employee pole DateTime birthDate zawierające datę urodzenia pracownika.
Dodaj na nim adnotacje
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
Spróbuj dokonać serializacji a następnie deserializacji obiektu tak zmodyfikowanej klasy.
Wskazówka
Możesz potrzebować modułu jackson-datatype-joda.
Potrzebujesz zarejestrować w mapperze moduł Joda.
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ę:
@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
Projekt z zakresu 3-4
Stworzenie programu, który będzie zapisywał i odczytywał określony model danych.
Każda osoba musi obmyślić jakiego typu dane chce przechowywać w końcowej aplikacji (informacje o osobach, filmach, autach, piłkarzach, komputerach etc.) Należy zamodelować te dane poprzez określenie pól i zależności między nimi.
Dane muszę zawierać co najmniej dwie relacje OneToOne i dwie relacje OneToMany (lub ManyToMany). Do danych należy stworzyć zapytania (najlepiej użyteczne w docelowym projekcie). Zapytań musi być co najmniej 5, każde zapytanie powinno być odrębną metodą w kodzie. Jedno z zapytań powinno być stronicowane.
Należy użyć w projekcie pola do zapisu daty - najlepiej ZonedDateTime (jako, że może to przysporzyć problemu, w przypadku braku użycia daty max liczba punktów do zdobycia to 85%).
Docelowy projekt powinien czytać i zapisywać zamodelowane dane z i do plików typu XML i JSON (obu typów!) i zapisywać/odczytywać je z bazy danych.
Program powinno byś w stanie się uruchomić. Nie trzeba tworzyć żadnego menu do projektu, sprawdzenie poprawności może odbywać się poprzez zdefiniowanie odpowiednich testów dla scenariuszy: odczyt danych z pliku XML / JSON i zapis do bazy, odczyt danych z bazy i zapis do plików XML / JSON. Należy stworzyć odpowiednie pliki XML / JSON dla ww testów.
Uwaga! odczyt i zapis powinien się powodzić / niepowodzić w przypadku podania nieprawidłowych czy częściowych danych (pewne pola puste). Model danych powinien być zabezpieczony przed rekurencyjnymi odwołaniami przy serializacji.
Proponuję kod dołączyć u siebie na repozytorium w gałęzi master.