Różne formaty danych

Deserializacja JSONa – o co chodzi? 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. Podsumowując 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. Po pierwsze przez 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 – deserializacja JSONa

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ć. Przede wszystkim 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.

To co analizowaliśmy to tylko deserializacja formatu JSON. Co więcej 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.”