Różne formaty danych

Kiedy pobieramy w naszym programie dane z Internetu, najczęściej są one w jednym z dwóch formatów zapisu: JSON (którym dzisiaj się zajmiemy) lub XML. Przykładowe dane w formacie JSON pobrane z serwisu Narodowego Banku Polskiego:
 {
  „table”: „A”,
  „currency”: „euro”,
  „code”: „EUR”,
  „rates”: [
               {
               „no”: „151/A/NBP/2019”,
              „effectiveDate”: „2019-08-06”,
              „mid”: 4.3096
              }
  ]
}
Rys 1. Odpowiedź w formacie JSON z serwisu NBP pod adresem: http://api.nbp.pl/api/exchangerates/rates/a/eur?format=json.

Pobieranie tekstu z Internetu

Chcąc odczytać zawartość pliku na naszym komputerze, musimy użyć ścieżki do pliku (np. C:\Program Files\Java) oraz klasy File lub Path. W przypadku zawartości dostępnej w Internecie, postępujemy analogicznie – używamy adresu do zasobu internetowego w kodzie (najczęściej zaczynającego się od http:// lub https://) oraz klasy URL:
                 URL nbpUrl = new URL(„http://api.nbp.pl/api/exchangerates/rates/a/eur?format=json”);
 Następnie musimy nawiązać połączenie z podanym adresem – najprościej to zrobić używając metody openStream:
                InputStream nbpInputStream = nbpUrl.openStream();
 W rezultacie otrzymujemy dobrze znany nam obiekt klasy InputStream – który łatwo możemy odczytać za pomocą Scanner’a lub BufferedReader’a. My skorzystamy z tego pierwszego:
                String nbpText = scanner.nextLine();
Jeżeli wyświetlimy zawartość zmiennej nbpText, to jest to dokładnie treść naszego JSONa:
{„table”:”A”,”currency”:”euro”,”code”:”EUR”,”rates”:[{„no”:”151/A/NBP/2019″,”effectiveDate”:”2019-08-06″,”mid”:4.3096}]}

W formacie JSON odstępy (spacje) nie mają żadnego znaczenia – najczęściej wyświetlamy tekst ładnie sformatowany (jak na Rys. 1), natomiast między komputerami przesyłamy w nieco bardziej „surowej” postaci – bez odstępów, aby zajmował jak najmniej miejsca (jak powyżej).
Jeżeli opisany powyżej kod zamkniemy do osobnej metody i dodamy trzy użyteczne potrzebne poprawki (zamykanie używanych zasobów, obsługę wyjątków oraz odczytanie wszystkich linijek z wejścia), otrzymamy następujący kod:

public static String downloadJsonFromURL(String urlText) {
              try {
              URL myUrl = new URL(urlText);
              StringBuilder jsonText = new StringBuilder();
              try (InputStream myInputStream = myUrl.openStream();
              Scanner scanner = new Scanner(myInputStream)) {
              while (scanner.hasNextLine()) {
                           jsonText.append(scanner.nextLine());
                }
                       return jsonText.toString();
             }
             } catch (IOException e) {
             System.err.println(„Failed to get content from URL ” + urlText + ” due to exception: ” + e.getMessage());
             return null;
             }
}
Rys 2. Gotowy kod na pobranie tekstu z zadanego URLa.

Deserializacja JSONa

Z pobranego tekstu chcielibyśmy wydzielić wartość kursu dla danego dnia. Samodzielne przetwarzanie tego tekstu to dość karkołomne zadanie i bardzo łatwo popełnić błąd. Musielibyśmy obliczyć, ze wartość kursu znajduje się pomiędzy indeksem 111 a 117 i wydzielić te znaki ze Stringa – ale przecież coś może się zmienić (np. dla innej waluty) i wówczas odczytamy bzdury… Moglibyśmy też szukać w Stringu wartości mid, a po niej opuścić dwa znaki i zacząć odczytywać liczbę – ale co w sytuacji, gdybyśmy mieli wiele pól o takiej samej nazwie? Czy na pewno odczytamy to, które nas interesuje?
To, co chcemy zrobić, nazywa się deserializacją JSONa – chcemy zamienić ten tekstowy format na coś dużo łatwiejszego dla nas w użyciu. W przypadku Javy wybór docelowej postaci jest bardzo prosty – chcielibyśmy z tego JSONa otrzymać Javowy obiekt. Zamiast robić to samodzielnie i martwić się o wszystkie problemy – dużo łatwiej będzie nam skorzystać z gotowej biblioteki, która za nas je rozwiąże.
Do wyboru mamy wiele różnych bibliotek, dzięki którym dostaniemy pożądany rezultat. Osobiście preferuję użycie biblioteki Gson od Google – jest bardzo intuicyjna i łatwo jej użyć w aplikacji. Jeżeli korzystamy z JavyEE / Springa, prawdopodobnie lepiej byłoby skorzystać z czegoś wbudowanego w serwer aplikacji, natomiast my w tym tekście skupiamy się na JavieSE.

Pobranie biblioteki Gson

Biblioteka Gson jest dostępna w Maven Central pod tym adresem. Jeżeli korzystamy z projektu mavenowego, nie musimy pobierać i dodawać biblioteki do projektu samodzielnie – wystarczy do pliku pom.xml dodać zależności. Robimy to, zapisując wewnątrz tagu <project> następujące linijki:
<dependencies>
           <dependency>
           <groupId>com.google.code.gson</groupId>
           <artifactId>gson</artifactId>
           <version>2.8.5</version>
           </dependency>
</dependencies>
Rys 3. Zależności mavenowe dla biblioteki Gson
Dzięki temu Maven samodzielnie pobierze bibliotekę i doda ją do naszego projektu – a my możemy z niej skorzystać.

Użycie Gsona do deserializacji

Zanim faktycznie odczytamy Javowy obiekt z JSONa, musimy przygotować dla niego klasę. W naszym przypadku będą potrzebne aż dwie. Ich zawartość odpowiada jeden do jednego wszystkim polom które znajdziemy w naszym JSONie:
public class Currency {
             public String table;
             public String currency</b>;
             public String code;
             public Rate[] rates;
}
public class Rate {
             public String no;
             public String effectiveDate;
             public double mid;
}
Rys 4. Klasy Currency i Rate. Dla uproszczenia i czytelności wszystkie pola są publiczne.
Warto teraz spojrzeć ponownie na Rys 1. – widać wyraźnie, że każda wartość z JSONa ma swoje pole w klasie Javowej. Bardzo ważne jest, żeby nazwa pola w klasie Javowej była taka sama jak nazwy pola w JSONie – biblioteka Gson właśnie tej z nazwy korzysta, żeby przepisać wartości w odpowiednie miejsce.
Podpowiedź:
Jeżeli jakieś pola z JSONa nas nie interesują, możemy ich nie zapisywać w definicji klasy – zostaną wówczas pominięte.
Gdybyśmy chcieli użyć innej nazwy pola w Javie niż ta, która jest w JSONie, możemy to zrobić przez wskazanie Gsonowi, z jakiej nazwy ma skorzystać. Robimy to za pomocą poniższej adnotacji:
               @SerializedName(„nameFromJSON”)
                public String nameFromJavaCode;
Kiedy mamy już to wszystko przygotowane, potrzebujemy tylko trzech linijek kodu, by stworzyć Javowe obiekty przechowujące interesujące nas informacje:
               String nbpJson = downloadJsonFromURL(„http://api.nbp.pl/api/exchangerates/rates/a/eur?format=json”);
               Gson gson = new Gson();
               Currency eurCurrency = gson.fromJson(nbpJson, Currency.class);
W pierwszej linijce korzystamy z metody z Rys 2. W kolejnej tworzymy nowy obiekt klasy Gson, który posłuży nam w ostatniej linijce. Tam korzystamy z metody fromJson, która pozwala zamienić tekst na obiekt określonej klasy. Jako pierwszy parametr przekazujemy treść naszego JSONa pobranego w pierwszej linii, a jako drugi parametr – klasę, jakiej obiekt chcemy uzyskać w wyniku tej deserializacji. Wystarczy ten punkt zaczepienia, a Gson sam odczyta, jakie pola są wewnątrz tej klasy i przypisze im odpowiednie wartości.
Po tych instrukcjach możemy już korzystać z naszego obiektu. Wspomniany wcześniej kurs waluty odczytamy w ten sposób:
               double eurRateValue = eurCurrency.rates[0].mid;
               System.out.println(„Today for 1 EUR you’ll get ” + eurRateValue + ” PLN”);
Możemy również odczytać dowolne inne pola. Gotowy kod jest dostępny jako projekt do pobrania tutaj 
 Podpowiedź:
Warto zauważyć, że Gson samodzielnie poradził sobie z deserializacją tablicy w JSONie, która kryje się pod nazwą rates. Zapisaliśmy, że jest to javowa tablica obiektów klasy Rate, natomiast nic nie stoi na przeszkodzie, abyśmy zamienili taką tablicę na Listę – wystarczy zapisać ją jako public List<Rate> rates;

Podsumowanie

W tym tekście użyliśmy biblioteki Gson do zamiany pobranego z internetu JSONa na obiekty w naszej aplikacji Javowej, z których możemy z łatwością korzystać. Możliwości biblioteki Gson są oczywiście dużo szersze, w szczególności potrafi również działać w drugą stronę – zamieniać Javowe obiekty na tekst w formacie JSON, który możemy np. zapisać do pliku czy przesłać przez Internet.
W tekście analizowaliśmy tylko deserializację formatu JSON. Zrobienie tego dla formatu XML wymaga analogicznych czynności, ale innych narzędzi (biblioteki). Mierzącym się z tym problemem polecam spojrzeć na bibliotekę XStream, która działa na bardzo podobnej zasadzie co Gson.

Grzegorz Witczak

„Programista Java z JIT Team z zamiłowaniem do baz danych oraz upraszczania kodu i życia. Poza programowaniem lubię dzielić się wiedzą jako prelegent i trener programowania. Po godzinach: gadżeciarz, pasjonat bezpieczeństwa IT i early adopter, śmigający po Trójmieście na elektrycznej hulajnodze.”