Rozdział 19. Przetwarzanie danych w formacie JSON

JSON, czyli JavaScript Object Notation, jest:

  • Prostym i „lekkim” formatem wymiany danych.

  • Prostszą i bardziej zwięzłą alternatywą dla języka XML (patrz Rozdział 20.).

  • Formatem zapisu danych, które można w prosty sposób generować, używając wywołań metody println() lub jednego z kilku dostępnych interfejsów programistycznych.

  • Formatem rozpoznawanym przez interpretery JavaScript zaimplementowane we wszystkich przeglądarkach WWW.

  • Obsługiwany przez wiele pakietów narzędziowych dla wielu różnych języków programowania (Java, C/C++, Perl, Ruby, Python, Lua, Erlang, Haskell i wiele innych); kuriozalnie długą listę języków, w których można korzystać z tego formatu, oraz służących do tego narzędzi (w tym ponad 25 przeznaczonych do użycia w Javie) można znaleźć na poświęconej mu stronie WWW (http://json.org/).

Poniżej przedstawiłem prosty przykład danych zapisanych w formacie JSON:

/main/resources/json/softwareinfo.json

{
  "name": "robinparse",
  "version": "1.2.3",
  "description": "Kolejny analizator formatu JSON",
  "className": "RobinParse",
  "contributors": [
        "Robin Smythe",
        "Jon Jenz",
        "Jan Ardann"
    ]
}

Jak widać, składnia tego kodu jest prosta, ma hierarchiczną strukturę i jest czytelna dla człowieka.

W witrynie WWW poświęconej temu formatowi (http://json.org/) można znaleźć zwięzły opis jego składni. Wyróżnia ona dwa rodzaje struktur: obiekty formatu JSON (mapy) oraz tablice formatu JSON (listy). Obiekty formatu JSON są parami składającymi się z klucza oraz wartości, które w Javie można reprezentować przy użyciu obiektów typu java.util.Map lub jako właściwości zwyczajnych obiektów Javy. Na przykład pola obiektu LocalDate (patrz „6.1. Określanie bieżącej daty”) reprezentującego datę 1 kwietnia 2014 roku można zapisać w formacie JSON w następujący sposób:

{
    "year": 2014,
    "month": 4,
    "day": 1
}

Z kolei tablice formatu JSON są uporządkowanymi listami, które w Javie można reprezentować przy użyciu tablic lub obiektów typu java.util.List. Lista zawierająca dwie daty może wyglądać jak w poniższym przykładzie:

{
    [{
        "year": 2014,
        "month": 4,
        "day" : 1
    },{
        "year": 2014,
        "month": 5,
        "day" : 15
    }]
}

JSON jest formatem zapewniającym pewną elastyczność, dlatego też dane z ostatniego przykładu można równie dobrze zapisać w poniższej postaci, która jest może nieco mniej czytelna dla nas, ludzi, lecz zawiera dokładnie te same informacje i gwarantuje dokładnie te same możliwości funkcjonalne:

{[{"year":2014,"month":4,"day":1},{"year":2014,"month":5,"day":15}]}

Jestem przekonany, że napisano już setki analizatorów formatu JSON. Oto kilka z nich, które przychodzą mi na myśl w kontekście korzystania z tego formatu w programach pisanych w Javie:

stringtree.org

Bardzo niewielki i prosty.

Analizator json.org

Bardzo często używany, gdyż jest darmowy, a adres jego strony WWW można łatwo zapamiętać.

Analizator jackson.org

Powszechnie używany, gdyż daje duże możliwości i jest stosowany w szkielecie Spring Framework.

javax.json

Nowy, oficjalny analizator wybrany przez firmę Oracle, choć obecnie jest używany standardowo tylko w korporacyjnej wersji języka Java.

W tym rozdziale przedstawiłem kilka sposobów przetwarzania danych zapisanych w formacie JSON przy użyciu powyższych interfejsów programistycznych. Nieco niepokojący jest fakt, że „oficjalne” API, javax.json, jest dołączane wyłącznie do korporacyjnej wersji języka Java, Java EE, dlatego jest raczej mało prawdopodobne, by było ono powszechnie dostępne na komputerach klienckich. Niektóre z nazw stosowanych w tym API odpowiadają nazwom używanym w pakiecie org.json, lecz nie jest ich na tyle dużo, by można je było uznać za „zgodne”.

Ponieważ w tej książce koncentrujemy się na standardowej, a nie korporacyjnej wersji języka Java, nie będę w niej wspominał o bezpośrednim przetwarzaniu danych w formacie JSON przy użyciu generowanego przez serwer i wykonywanego w przeglądarce kodu JavaScript, choć takie rozwiązania mogą być bardzo użyteczne w przypadku tworzenia aplikacji korporacyjnych.

Z pakietu Jackson można korzystać na wiele różnych sposobów. W prostych przypadkach można prawie automatycznie konwertować zwyczajne obiekty Javy (POJO) na format JSON lub je odtwarzać. Przykład takich operacji został przedstawiony na Przykład 19-1.

Wykonanie tego przykładu spowoduje wyświetlenie następujących wyników:

Odczyt i przetworzenie obiektu Person z formatu JSON: Robin Wilson
Obiekt Person Roger Rabbit zapisany w formacie JSON = {"id":0,"firstName":"Roger","lastName":"Rabbit","fullName":"Roger Rabbit"}

Kolejny kod przedstawia przykład, który odczytuje plik z danymi zapisanymi w formacie JSON, przedstawiony na samym początku tego rozdziału. W klasie SoftwareInfo warto zwrócić uwagę na pole contributors, które zostało zadeklarowane jako List<String>.

/json/SoftwareParseJackson.java

public class SoftwareParseJackson {
    final static String FILE_NAME = "/json/softwareinfo.json";

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();       ➊

        InputStream jsonInput =
            SoftwareParseJackson.class.getResourceAsStream(FILE_NAME);
        if (jsonInput == null) {
            throw new NullPointerException("Nie można odnaleźć pliku" + FILE_NAME);
        }
        SoftwareInfo sware = mapper.readValue(jsonInput, SoftwareInfo.class);
        System.out.println(sware);
    }
}
  • ➊ Obiekt ObjectMapper zajmuje się wszystkimi czynnościami związanymi z analizą kodu JSON.

Wykonanie tego przykładu spowoduje wyświetlenie następujących wyników:

Program: robinparse (1.2.3); Autorzy: [Robin Smythe, Jon Jenz, Jan Ardann]

Oczywiście można wskazać przypadki, w których odwzorowywanie kodu JSON na obiekty jest znacznie bardziej złożone; z myślą o takich sytuacjach pakiet Jackson udostępnia zestaw adnotacji pozwalających na kontrolowanie odwzorowań. Niemniej jednak domyślny sposób odwzorowywania jest całkiem dobry!

Pakiet org.json nie jest aż tak zaawansowany jak Jackson ani nie operuje na tak wysokim poziomie; zmusza on nas do myślenia i działania w kategoriach abstrakcji formatu JSON, a nie kodu Javy. Na przykład poniżej przedstawiłem program, który korzystając z tego pakietu, odczytuje plik z opisem oprogramowania przedstawiony na początku tego rozdziału:

/json/SoftwareParseOrgJson.java

public class SoftwareParseOrgJson {
    final static String FILE_NAME = "/json/softwareinfo.json";

    public static void main(String[] args) throws Exception {

        InputStream jsonInput =
            SoftwareParseOrgJson.class.getResourceAsStream(FILE_NAME);
        if (jsonInput == null) {
            throw new NullPointerException("Nie mogę znaleźć pliku" + FILE_NAME);
        }
        JSONObject obj = new JSONObject(new JSONTokener(jsonInput));      ➊
        System.out.println("Nazwa programu: " + obj.getString("name"));   ➋
        System.out.println("Numer wersji: " + obj.getString("version"));
        System.out.println("Opis: " + obj.getString("description"));
        System.out.println("Klasa: " + obj.getString("className"));
        JSONArray contribs = obj.getJSONArray("contributors");            ➌
        for (int i = 0; i < contribs.length(); i++) {                     ➍
            System.out.println("Personalia współpracownika: " + contribs.get(i));
        }
    }

}
  • ➊ Tworzymy obiekt JSONObject na podstawie danych wejściowych.

  • ➋ Pobieramy poszczególne pola zawierające łańcuchy znaków.

  • ➌ Pobieramy obiekt JSONArray zawierający personalia osób, które mają zostać zapisane w polu contributors.

  • ➍ Klasa org.json.JSONArray nie implementuje interfejsu Iterable, dlatego nie możemy zastosować tu pętli „foreach”.

Wykonanie tego programu spowoduje wyświetlenie następujących wyników:

Nazwa programu: robinparse
Numer wersji: 1.2.3
Opis: Kolejny analizator formatu JSON
Klasa: RobinParse
Personalia współpracownika: Robin Smythe
Personalia współpracownika: Jon Jenz
Personalia współpracownika: Jan Ardann

Klasy JSONObject oraz JSONArray udostępniają metody toString(), które generują łańcuchy znaków zawierające prawidłowo zapisane dane w formacie JSON. Oto przykład ich zastosowania:

/json/WriteOrgJson.java

public class WriteOrgJson {
    public static void main(String[] args) throws JSONException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Name", "robinParse").        ➊
            put("Version", "1.2.3").
            put("Class", "RobinParse");
        String printable = jsonObject.toString();    ➋
        System.out.println(printable);
    }
}