5. kodutöö (alternatiiv): GSON
See on alternatiivne kodutöö nendele, kes tahaks midagi natuke elulisemat kui see sügav arusaamine lekseri käitumisest. Kuigi ma soovitan valida lekseri kodutöö ja selle vahel, siis tegelikult võib ükskõik millise kodutöö sellega asendada. Seega, meil tuleb kokku 12 kodutööd, aga kodutööde eest saab maksimaalselt 10 punkti.
JSON (JavaScript Object Notation) on universaalselt kasutusel olev tekstipõhine andmevahetusformaat. JSON põhineb küll JavaScripti objekt süntaksil kuid on kasutatav kõikjal. JSON-it kasutatakse väga tihti veebirakendustes, näiteks kliendi ja serveri vahel info edastamiseks. JSON koosneb võtmete ja väärtuste paaridest (nagu Java HashMap ja Pythoni Dictionary). Väärtused võivad olla:
- Numbrid
- Tõeväärtused
- Sõned
- Massiivid
- Teised objektid
null
Võtmed käivad jutumärkide sisse. Võti ja väärtus on eraldatud kooloniga. Võtme ja väärtuse paare võib olla mitu, eraldatud komaga. Näide ühest JSON'ist, kus on kasutatud kõiki väärtuse tüüpe:
{ "Name": "Andres", "Adult": true, "Courses": ["AKT", "Keeletehnoloogia"], "Car": { "Color": "yellow", "Horsepower": 180 } }
GSON
Selle kodutöö piires õpime GSON-i. See on Google poolt loodud teek JSON-i parsimiseks. Selle lisame sõltuvus järgmisele teegile Gradle'i konfiguratsioonifailis: implementation 'com.google.code.gson:gson:2.10.1'
(See on kursuse repositooriumis juba tehtud.)
Näidetes kasutame lihtsat Student klassi, millel on vaid paar atribuuti ja konstruktor:
public class Student { String firstname; String surname; int age; public Student(String firstname, String surname, int age) { this.firstname = firstname; this.surname = surname; this.age = age; } }
Serialiseerimine
Tuletame meelde, näiteks OOPi loengust, et serialiseerimine on objekti teisendamine andmejadaks, mida saab salvestada. On erinevaid formaate, kuidas objekte esitada, aga tähtis on serialiseerimise juures, et esitus võimaldaks pärast esialgset objekti taastada ehk deserialiseerida. Erinevalt Java enda serialiseerimisest on Json inimloetav ja redigeeritav tekstifail.
Saame serialiseerida igasuguseid Java objekte väga lihtsalt ToJson meetodiga:
Gson gson = new Gson(); // Loome uue Student objekti Student student = new Student("Andres", "Paas", 24); // Parsime objekti JSON stringiks String json = gson.toJson(student); System.out.println(json);
Vastus:
{"firstname":"Andres", "surname":"Paas", "age":24}
GSON parseril on mitmeid seadistusi, mida saame muuta. Üldiselt pole seda vaja teha, aga on hea teada, et need on olemas. Selleks kasutame GsonBuilder klassi, mille peal saame välja kutsuda erinevaid konfiguratsiooni meetodeid. Kõige viimasena kutsume välja create meetodit, mis loob meie GSON isendit. Järgnevas näites kasutame nime seadistamise meetodit ja ilutrüki meetodit:
Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) // Muudab atribuutide nimede esitähed suureks .setPrettyPrinting() // Prindib atribuudid välja eraldi ridadel .create(); // Loome uue isik objekti Student student = new Student("Andres", "Paas", 24); // Parsime objekti JSON stringiks String json = gson.toJson(student); System.out.println(json);
Vastus:
{ "Firstname": "Andres", "Surname": "Paas", "Age": 24 }
Deserialiseerimine
Peatume nüüd natuke pikemalt deserialiseerimisele, sest seda läheb praktikas rohkem vaja. Näiteks kui meil on vaja REST API kaudu päring teha, siis vastuse saame JSON sõnena. Nüüd harjutamegi selle parsimist. Peamiselt on kaks viisi kuidas JSON-i deserialiseerida.
1. fromJson meetod otse Java objektiks
Kõige lihtsam meetod, seni kuni JSON-i atribuudid on samade nimedega mis meie Java klassi atribuudid, on lihtsalt kasutada fromJson meetodit. See meetod võtab esimeseks argumendiks kas JsonReader objekti või lihtsalt stringi milles on JSON. Teine argument on tüüp, milleks me tahame parsida, antud juhul Student.
Loeme JSONi sisse failist
Gson gson = new Gson(); JsonReader reader = new JsonReader(new FileReader("input.json")); Student student = gson.fromJson(reader, Student.class);
input.json faili sisu:
{ "firstname": "Pipi", "surname": "Pikksukk", "age": 20, "courses": ["Sissejuhatus Erialasse", "Programmeerimine", "Matemaatiline maailmapilt"] }
Siinjuures tooks tähelepanu courses atribuudi juurde. Seda atribuuti meie Student objektis ei leidu, seega parser lihtsalt ignoreerib seda. See on oluline omadus, sest tänu sellele võime parsida väga suuri JSON faile, milles on palju ebaolulisi atribuute, millest siis parsime ainult need mis meile olulised.
NB: Kui mõni atribuut on puudu JSON-is siis parsides seda Java objektiks antakse sellele atribuudile lihtsalt null väärtus.
Annotatsioonid. GSON on küll loodud põhimõttega, et annotatsioone pole meie klassides vaja kasutada. Kuid mõnedel juhtudel on siiski objektide parsimist vaja kohandada ja seega on selleks olemas mitmeid annotatsioone. Näiteks on olukord, kus meie JSON sisendandmed võivad olla mitmes eri keeles. Eelnevalt sai mainitud, et fromJson meetod toimib ainult siis kui klassi atribuudid on samade nimedega mis JSON-i atribuudid. Sellest piirangust on võimalik ümber saada kui kasutame @SerialisedName annotatsiooni, et defineerida võimalikud aliased meie atribuutidele.
public class Student { @SerializedName(value = "firstname", alternate = {"nimi", "eesnimi"}) String firstname; @SerializedName(value = "surname", alternate = {"perekonnanimi", "perenimi"}) String surname; @SerializedName(value = "age", alternate = "vanus") int age; public Student(String firstname, String surname, int age) { this.firstname = firstname; this.surname = surname; this.age = age; } }
Nüüd saab GSON ka parsida järgnevat JSON stringi ilma probleemideta.
{ "nimi": "Pipi", "perenimi": "Pikksukk", "vanus": 20, }
2. JSON puu meetod
Kui me ei taha eraldi klassi luua (nagu meie näidetes kasutatud Student klass) selleks, et JSON-ist andmeid kätte saada, siis me võime ka individuaalseid atribuute JSON-ist välja noppida. Selleks peame esiteks parsima JSON-i JsonObject'iks.
String jsonString = Files.readString(Paths.get("input.json")); // Input faili sisu sama mis enne JsonObject jsonTree = JsonParser.parseString(jsonString).getAsJsonObject();
Paneme tähele, et tahame individuaalseid atribuute JSON-ist kätte saada, ja selle jaoks on vaja käsitleda JsonObject tüüpi, mitte JsonElement tüüpi. Kuna parseString tagastab JsonElement tüübi, siis peame kutsuma sellel veel .getAsJsonObject meetodit.
Peale seda on võimalik kõiki atribuute käsitleda lihtsalt nende atribuutide nimede kaudu, kasutades get meetodit. Näiteks kui tahame saada tudengi eesnime ja kursuseid kätte, saame seda teha järgnevalt:
String firstname = jsonTree.get("firstname").getAsString(); System.out.println(firstname); // Kuna kursused on massiivis, peame neid veidi teistmoodi käsitlema JsonArray courses = jsonTree.getAsJsonArray("courses"); for (Object course : courses ) { System.out.println(course.toString()); }
Vastus:
Pipi "Sissejuhatus Erialasse" "Programmeerimine" "Matemaatiline maailmapilt"
Kirjetüübid (record)
Kirjed on Javas leiduv muutumatu andmeklass, mis nõuab ainult defineerida atribuutide tüüpe ja nimesid. Saame kirjeklassi defineerida mugavalt ühel real ja konstruktor luuakse meie eest. Näiteks eelnevalt kasutatud Student klass näeks kirjena välja nõnda:
public record Student(String firstname, String surname, int age) {}
Automaatselt luuakse siis kõik need toString, equals ja hashcode meetodeid, aga juurdepääsumeetodite nimedeks ei ole siin getFirstname(), vaid lihtsalt firstname(). Kui on vaja selliste nimedega meetodeid, siis saab neid muidugi kirjeklassile lisada.
Ülesanded
Ülesannete juures võite kasutada ükskõik millist eelpool näidatud meetoditest. Kui rakendate fromJson meetodit siis peate vastava Java klassi ise looma (nagu näidetes kasutatud Student klass).
getTitleIsbn
: Tagastage ISBN kood raamatule, mis antud argumendis.filterByGenre
: Tagastage JSON string mis sisaldab kõiki raamatuid mis on argumendis antud žanris.getAuthorsByPublisher
: Tagastage Set kõikidest autoritest keda on kirjastanud argumendina antud kirjastus.mostPagesInGenre
: Tagastage milline autor on kirjutanud kõige rohkem argumendis antud žanrist.getAsPublication
Looge klass mis implementeerib järgnevat Publication interface. Parsige raamat mille tiitel on antud argumendina selleks objektiks, ning tagastage see.
public interface Publication { String getTitle(); double getPrice(); List<String> getAuthors(); }
Ülesanne lahendamiseks vajalik kood asub kursuse repositooriumis paketis week5.altgson. Lahenduse saab üles laadida siin.