Lekcja 19: JSON - Praca z Danymi w Formacie JSON

Witaj w dziewiętnastej lekcji kursu PHP! W dzisiejszym świecie aplikacji internetowych wymiana danych między różnymi systemami i technologiami jest codziennością. Jednym z najpopularniejszych formatów używanych do tego celu jest JSON (JavaScript Object Notation). Jest to lekki, tekstowy format wymiany danych, czytelny zarówno dla ludzi, jak i łatwy do parsowania przez maszyny. PHP oferuje wbudowane, wydajne funkcje do pracy z JSON, które pozwalają na łatwe kodowanie danych PHP do formatu JSON oraz dekodowanie danych JSON na zmienne PHP. W tej lekcji dokładnie przyjrzymy się formatowi JSON, jego składni oraz sposobom jego obsługi w PHP.

Co to jest JSON?

JSON, czyli JavaScript Object Notation, został pierwotnie wywiedziony z języka JavaScript (konkretnie ze sposobu zapisu literałów obiektów i tablic w JavaScript), ale obecnie jest formatem niezależnym od języka programowania. Jego prostota, czytelność i łatwość implementacji parserów sprawiły, że stał się de facto standardem w wielu obszarach, szczególnie w komunikacji z API webowymi (REST API).

Kluczowe cechy JSON:

Podstawowa Składnia JSON

JSON opiera się na dwóch podstawowych strukturach:

  1. Kolekcja par nazwa/wartość (Obiekt): Jest to nieuporządkowany zbiór par klucz-wartość. W JSON obiekt jest otoczony nawiasami klamrowymi {}. Klucze muszą być stringami (ujęte w podwójne cudzysłowy), a wartości mogą być dowolnego typu JSON. Pary klucz-wartość są oddzielone przecinkami, a klucz od wartości dwukropkiem. Odpowiada to obiektom w JavaScript lub tablicom asocjacyjnym w PHP.
  2. Uporządkowana lista wartości (Tablica): Jest to uporządkowana sekwencja wartości. W JSON tablica jest otoczona nawiasami kwadratowymi []. Wartości są oddzielone przecinkami i mogą być dowolnego typu JSON. Odpowiada to tablicom indeksowanym w PHP lub tablicom w JavaScript.

Typy Danych w JSON:

Przykład struktury JSON:

{
  "imie": "Jan",
  "nazwisko": "Kowalski",
  "wiek": 30,
  "czyAktywny": true,
  "adres": {
    "ulica": "Kwiatowa",
    "numerDomu": "10A",
    "miasto": "Warszawa",
    "kodPocztowy": "01-234"
  },
  "jezyki": [
    "polski",
    "angielski"
  ],
  "ostatnieLogowanie": null
}

W powyższym przykładzie widzimy obiekt główny z kluczami takimi jak "imie", "nazwisko", "adres" (którego wartością jest kolejny obiekt), "jezyki" (którego wartością jest tablica stringów) oraz "ostatnieLogowanie" z wartością null.

Praca z JSON w PHP

PHP dostarcza dwie główne funkcje do pracy z JSON, dostępne od PHP 5.2.0, które są częścią domyślnej konfiguracji PHP (nie wymagają instalacji dodatkowych rozszerzeń, chyba że PHP zostało skompilowane z opcją --disable-json).

Kodowanie Danych PHP do JSON - Funkcja json_encode()

Funkcja json_encode() służy do konwersji zmiennej PHP (najczęściej tablicy lub obiektu) na jej reprezentację w formacie JSON (string).

Składnia: string|false json_encode(mixed $value, int $flags = 0, int $depth = 512)

Funkcja zwraca string JSON w przypadku sukcesu lub false w przypadku błędu (chyba że użyto JSON_THROW_ON_ERROR).

<?php
// Przykład 1: Prosta tablica asocjacyjna
$dane_uzytkownika_php = [
    "id" => 101,
    "imie" => "Anna",
    "email" => "anna.nowak@example.com",
    "tagi" => ["php", "webdev", "kurs"]
];

$json_string = json_encode($dane_uzytkownika_php);
echo "<h3>Prosta tablica asocjacyjna:</h3>";
echo "<pre>" . htmlspecialchars($json_string) . "</pre>";
// Wynik: {"id":101,"imie":"Anna","email":"anna.nowak@example.com","tagi":["php","webdev","kurs"]}

// Przykład 2: Z flagą JSON_PRETTY_PRINT
$json_pretty_string = json_encode($dane_uzytkownika_php, JSON_PRETTY_PRINT);
echo "<h3>Z JSON_PRETTY_PRINT:</h3>";
echo "<pre>" . htmlspecialchars($json_pretty_string) . "</pre>";
/* Wynik:
{
    "id": 101,
    "imie": "Anna",
    "email": "anna.nowak@example.com",
    "tagi": [
        "php",
        "webdev",
        "kurs"
    ]
}
*/

// Przykład 3: Polskie znaki i JSON_UNESCAPED_UNICODE
$dane_polskie = [
    "miasto" => "Łódź",
    "opis" => "Zażółć gęślą jaźń"
];

$json_polskie_escaped = json_encode($dane_polskie, JSON_PRETTY_PRINT);
echo "<h3>Polskie znaki (domyślnie escaped):</h3>";
echo "<pre>" . htmlspecialchars($json_polskie_escaped) . "</pre>";
/* Wynik:
{
    "miasto": "\u0141\u00f3d\u017a",
    "opis": "Za\u017c\u00f3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144"
}
*/

$json_polskie_unescaped = json_encode($dane_polskie, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
echo "<h3>Polskie znaki (z JSON_UNESCAPED_UNICODE):</h3>";
echo "<pre>" . htmlspecialchars($json_polskie_unescaped) . "</pre>";
/* Wynik:
{
    "miasto": "Łódź",
    "opis": "Zażółć gęślą jaźń"
}
*/

// Przykład 4: Kodowanie obiektu PHP
class Produkt {
    public $nazwa;
    public $cena;
    private $id_wewnetrzne; // Prywatne właściwości nie są domyślnie kodowane

    public function __construct($nazwa, $cena, $id_wewnetrzne) {
        $this->nazwa = $nazwa;
        $this->cena = $cena;
        $this->id_wewnetrzne = $id_wewnetrzne;
    }
}

$produkt_obj = new Produkt("Super Gadżet", 79.99, "XYZ123");
$json_obiekt = json_encode($produkt_obj, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
echo "<h3>Kodowanie obiektu PHP:</h3>";
echo "<pre>" . htmlspecialchars($json_obiekt) . "</pre>";
// Wynik (tylko publiczne właściwości):
// {
//     "nazwa": "Super Gadżet",
//     "cena": 79.99
// }

// Aby zakodować również prywatne/chronione właściwości, obiekt musi implementować interfejs JsonSerializable
// lub można użyć sztuczki z konwersją na tablicę (ale to nie zawsze idealne).

// Przykład 5: Obsługa błędów z JSON_THROW_ON_ERROR (PHP >= 7.3)
$niepoprawne_dane_utf8 = "\xB1\x31"; // Niepoprawny ciąg UTF-8
try {
    $json_error = json_encode($niepoprawne_dane_utf8, JSON_THROW_ON_ERROR);
    echo $json_error;
} catch (JsonException $e) {
    echo "<h3>Błąd kodowania JSON (JsonException):</h3>";
    echo "Wiadomość: " . $e->getMessage() . "<br>"; // Np. "Malformed UTF-8 characters, possibly incorrectly encoded"
    echo "Kod: " . $e->getCode() . "<br>";
}
?>

Dekodowanie Danych JSON do PHP - Funkcja json_decode()

Funkcja json_decode() służy do konwersji stringa JSON na zmienną PHP (domyślnie obiekt typu stdClass, lub tablicę asocjacyjną, jeśli podano odpowiednią opcję).

Składnia: mixed json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0)

Funkcja zwraca zdekodowaną wartość PHP (obiekt, tablicę, string, liczbę, boolean lub null) w przypadku sukcesu, lub null w przypadku błędu (chyba że użyto JSON_THROW_ON_ERROR). Uwaga: null może być również poprawną wartością JSON, więc do sprawdzania błędów należy używać json_last_error() (lub wyjątków).

<?php
$json_data_string = 
    '{ "id": 205, "produkt": "Książka PHP", "cena": 99.50, "dostepny": true, "autor": { "imie": "Cezary", "nazwisko": "PHPowiec" }, "tagi": ["programowanie", "php", "web"] }';

// Przykład 1: Dekodowanie do obiektu stdClass (domyślnie)
$dane_php_obj = json_decode($json_data_string);

echo "<h3>Dekodowanie do obiektu stdClass:</h3>";
if ($dane_php_obj !== null) {
    echo "ID Produktu: " . htmlspecialchars($dane_php_obj->id) . "<br>";
    echo "Nazwa Produktu: " . htmlspecialchars($dane_php_obj->produkt) . "<br>";
    echo "Cena: " . htmlspecialchars($dane_php_obj->cena) . "<br>";
    echo "Dostępny: " . ($dane_php_obj->dostepny ? "Tak" : "Nie") . "<br>";
    echo "Autor: " . htmlspecialchars($dane_php_obj->autor->imie) . " " . htmlspecialchars($dane_php_obj->autor->nazwisko) . "<br>";
    echo "Tagi: " . htmlspecialchars(implode(", ", $dane_php_obj->tagi)) . "<br>";
} else {
    echo "Błąd dekodowania JSON!<br>";
}

// Przykład 2: Dekodowanie do tablicy asocjacyjnej
$dane_php_array = json_decode($json_data_string, true); // lub $associative = true

echo "<h3>Dekodowanie do tablicy asocjacyjnej:</h3>";
if ($dane_php_array !== null) {
    echo "ID Produktu: " . htmlspecialchars($dane_php_array["id"]) . "<br>";
    echo "Nazwa Produktu: " . htmlspecialchars($dane_php_array["produkt"]) . "<br>";
    echo "Cena: " . htmlspecialchars($dane_php_array["cena"]) . "<br>";
    echo "Dostępny: " . ($dane_php_array["dostepny"] ? "Tak" : "Nie") . "<br>";
    echo "Autor: " . htmlspecialchars($dane_php_array["autor"]["imie"]) . " " . htmlspecialchars($dane_php_array["autor"]["nazwisko"]) . "<br>";
    echo "Tagi: " . htmlspecialchars(implode(", ", $dane_php_array["tagi"])) . "<br>";
} else {
    echo "Błąd dekodowania JSON!<br>";
}

// Przykład 3: Obsługa błędów dekodowania (PHP >= 7.3 z JSON_THROW_ON_ERROR)
$niepoprawny_json_string = '{ "nazwa": "Test", "wiek: 30 }'; // Brak cudzysłowu przy kluczu wiek
try {
    $wynik = json_decode($niepoprawny_json_string, false, 512, JSON_THROW_ON_ERROR);
    // var_dump($wynik);
} catch (JsonException $e) {
    echo "<h3>Błąd dekodowania JSON (JsonException):</h3>";
    echo "Wiadomość: " . $e->getMessage() . "<br>"; // Np. "Syntax error"
    echo "Kod: " . $e->getCode() . "<br>";
}
?>

Obsługa Błędów JSON (dla PHP < 7.3 lub bez JSON_THROW_ON_ERROR)

Jeśli nie używasz JSON_THROW_ON_ERROR, musisz ręcznie sprawdzać błędy po wywołaniu json_encode() lub json_decode(). Służą do tego funkcje:

Możliwe kody błędów (stałe JSON_ERROR_*):

<?php
$bledny_json = "{ 'klucz': 'wartość' }"; // Użyto apostrofów zamiast cudzysłowów - błąd składni JSON

$dane = json_decode($bledny_json);

if ($dane === null && json_last_error() !== JSON_ERROR_NONE) {
    echo "<h3>Wystąpił błąd podczas dekodowania JSON:</h3>";
    echo "Kod błędu: " . json_last_error() . "<br>";
    if (function_exists('json_last_error_msg')) { // json_last_error_msg() od PHP 5.5
        echo "Komunikat błędu: " . json_last_error_msg() . "<br>";
    }
    // Dla $bledny_json powyżej, błędem będzie JSON_ERROR_SYNTAX
} elseif ($dane === null) {
    echo "Zdekodowano poprawnie, ale wynikiem jest null.<br>";
} else {
    echo "<h3>Dane zdekodowane pomyślnie (sposób tradycyjny):</h3>";
    // var_dump($dane);
}
?>

Używanie JSON_THROW_ON_ERROR z blokami try-catch jest jednak znacznie czystszym i nowocześniejszym podejściem do obsługi błędów JSON w PHP 7.3+.

Praktyczne Zastosowania JSON w PHP

  1. Komunikacja z API Webowymi (REST API): Wiele API (np. pogodowe, mapowe, social media) zwraca dane w formacie JSON. PHP może łatwo pobrać te dane (np. za pomocą cURL lub funkcji file_get_contents() z odpowiednimi ustawieniami strumienia) i zdekodować je za pomocą json_decode().
  2. Wysyłanie Danych do API: Podobnie, jeśli API oczekuje danych w formacie JSON w ciele żądania (np. POST, PUT), PHP może zakodować tablicę lub obiekt do JSON za pomocą json_encode() i wysłać.
  3. Konfiguracja Aplikacji: Pliki konfiguracyjne mogą być przechowywane w formacie JSON ze względu na jego czytelność i łatwość parsowania.
  4. Przechowywanie Danych w Bazach Danych NoSQL: Wiele baz danych NoSQL (np. MongoDB) używa formatu BSON (binarny JSON) lub bezpośrednio JSON do przechowywania dokumentów. PHP może łatwo przygotować dane w tym formacie. Nawet relacyjne bazy danych (np. MySQL, PostgreSQL) oferują typy JSON do przechowywania struktur JSON w kolumnach.
  5. Wymiana Danych z JavaScript po Stronie Klienta (AJAX): JSON jest naturalnym formatem do wymiany danych między PHP (backend) a JavaScript (frontend) w żądaniach AJAX. PHP może generować JSON, który jest następnie łatwo przetwarzany przez JavaScript.
<?php
// Przykład: Pobieranie danych z prostego publicznego API (JSONPlaceholder)
$url_api = "https://jsonplaceholder.typicode.com/users/1";

// Użycie file_get_contents (dla prostych GET, wymaga allow_url_fopen=On w php.ini)
// W produkcji lepiej użyć cURL dla większej kontroli i obsługi błędów.
$json_response = @file_get_contents($url_api);

if ($json_response === false) {
    echo "Nie udało się pobrać danych z API.<br>";
} else {
    try {
        $user_data = json_decode($json_response, true, 512, JSON_THROW_ON_ERROR);
        echo "<h3>Dane użytkownika z API:</h3>";
        echo "Imię: " . htmlspecialchars($user_data["name"]) . "<br>";
        echo "Email: " . htmlspecialchars($user_data["email"]) . "<br>";
        echo "Miasto: " . htmlspecialchars($user_data["address"]["city"]) . "<br>";
    } catch (JsonException $e) {
        echo "Błąd dekodowania odpowiedzi JSON z API: " . $e->getMessage() . "<br>";
    }
}

// Przykład: Generowanie JSON dla odpowiedzi AJAX
// header('Content-Type: application/json; charset=utf-8'); // Ważny nagłówek!
$odpowiedz_ajax = [
    "status" => "success",
    "wiadomosc" => "Operacja zakończona pomyślnie!",
    "dane" => ["id_rekordu" => 42, "nowa_wartosc" => "Zaktualizowano"]
];
// echo json_encode($odpowiedz_ajax, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
// exit;
?>

Bezpieczeństwo i Dobre Praktyki

Podsumowanie Lekcji

JSON jest niezwykle ważnym formatem wymiany danych we współczesnym programowaniu webowym. PHP dostarcza prostych, ale potężnych funkcji json_encode() i json_decode(), które umożliwiają efektywną pracę z tym formatem. Zrozumienie składni JSON, opcji dostępnych w funkcjach PHP oraz zasad bezpieczeństwa jest kluczowe dla każdego dewelopera PHP. Umiejętność kodowania tablic i obiektów PHP do JSON oraz dekodowania stringów JSON na struktury danych PHP otwiera drzwi do integracji z licznymi API i efektywnej komunikacji z aplikacjami klienckimi.

W następnej lekcji podsumujemy dotychczas zdobytą wiedzę z podstaw PHP, przygotowując się do bardziej zaawansowanych tematów.


Zadanie praktyczne

Stwórz skrypt PHP, który:

  1. Definiuje tablicę asocjacyjną PHP reprezentującą informacje o książce: tytuł, autor (jako kolejna tablica asocjacyjna z imieniem i nazwiskiem), rok wydania (liczba), ISBN (string) oraz listę tagów (tablica indeksowana stringów). Użyj polskich znaków w tytule i autorze.
  2. Koduje tę tablicę do formatu JSON, używając opcji JSON_PRETTY_PRINT oraz JSON_UNESCAPED_UNICODE. Wyświetl wynikowy string JSON (użyj htmlspecialchars() do wyświetlenia w HTML).
  3. Tworzy string JSON ręcznie (jako tekst), który zawiera błąd składniowy (np. brakujący przecinek lub cudzysłów).
  4. Próbuje zdekodować ten błędny string JSON używając json_decode() z opcją JSON_THROW_ON_ERROR w bloku try-catch. Wyświetl komunikat o błędzie, jeśli wystąpi.
  5. Próbuje zdekodować poprawny string JSON (ten wygenerowany w punkcie 2) z powrotem na tablicę asocjacyjną PHP. Wyświetl tytuł i imię autora z odkodowanej tablicy.

Pokaż przykładowe rozwiązanie
<?php
// 1. Definicja tablicy PHP z informacjami o książce
$ksiazka_php = [
    "tytul" => "Pan Tadeusz, czyli ostatni zajazd na Litwie",
    "autor" => [
        "imie" => "Adam",
        "nazwisko" => "Mickiewicz"
    ],
    "rokWydania" => 1834,
    "isbn" => "978-83-7779-590-1",
    "tagi" => ["epopeja narodowa", "romantyzm", "poemat", "lektura szkolna"]
];

echo "<h3>1. Dane książki w PHP:</h3>";
echo "<pre>";
print_r($ksiazka_php);
echo "</pre>";

// 2. Kodowanie do JSON z opcjami
$json_ksiazka = json_encode($ksiazka_php, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
echo "<h3>2. Zakodowany JSON:</h3>";
echo "<pre>" . htmlspecialchars($json_ksiazka) . "</pre>";

// 3. Błędny string JSON
$bledny_json_string = '{ "tytul": "Błędna Książka", "autor": "Nieznany" "rok": 2023 }'; // Brak przecinka po "Nieznany"

echo "<h3>4. Próba dekodowania błędnego JSON:</h3>";
try {
    $zdekodowany_bledny = json_decode($bledny_json_string, true, 512, JSON_THROW_ON_ERROR);
    echo "<p>Błędny JSON został (niespodziewanie) zdekodowany:</p>";
    echo "<pre>";
    print_r($zdekodowany_bledny);
    echo "</pre>";
} catch (JsonException $e) {
    echo "<p style='color:red;'>Wystąpił błąd podczas dekodowania błędnego JSON:<br>";
    echo "Komunikat: " . htmlspecialchars($e->getMessage()) . "<br>";
    echo "Kod: " . htmlspecialchars($e->getCode()) . "</p>";
}

// 5. Dekodowanie poprawnego JSON z powrotem do tablicy PHP
echo "<h3>5. Dekodowanie poprawnego JSON do tablicy PHP:</h3>";
try {
    $ksiazka_zdekodowana_php = json_decode($json_ksiazka, true, 512, JSON_THROW_ON_ERROR);
    echo "<p>Tytuł: " . htmlspecialchars($ksiazka_zdekodowana_php["tytul"]) . "</p>";
    echo "<p>Autor: " . htmlspecialchars($ksiazka_zdekodowana_php["autor"]["imie"]) . " " . htmlspecialchars($ksiazka_zdekodowana_php["autor"]["nazwisko"]) . "</p>";
} catch (JsonException $e) {
    echo "<p style='color:red;'>Wystąpił błąd podczas dekodowania poprawnego JSON:<br>";
    echo "Komunikat: " . htmlspecialchars($e->getMessage()) . "<br>";
    echo "Kod: " . htmlspecialchars($e->getCode()) . "</p>";
}

?>
            

Zadanie do samodzielnego wykonania

Napisz skrypt, który pobiera dane o losowym użytkowniku z publicznego API https://randomuser.me/api/. API to zwraca dane w formacie JSON.

  1. Użyj file_get_contents() (lub cURL, jeśli wolisz) do pobrania danych z API.
  2. Zdekoduj odpowiedź JSON do tablicy asocjacyjnej PHP. Pamiętaj o obsłudze błędów (zarówno pobierania, jak i dekodowania JSON).
  3. Wyświetl następujące informacje o użytkowniku: imię, nazwisko, płeć, adres email oraz pełny adres (ulica, miasto, kraj).
  4. Stwórz nową tablicę PHP zawierającą tylko wybrane przez Ciebie informacje (np. imię, nazwisko, email, zdjęcie-thumbnail). Zakoduj tę nową tablicę do formatu JSON z opcją JSON_PRETTY_PRINT i wyświetl wynik.


FAQ - Praca z JSON w PHP

Czy JSON jest lepszy niż XML?

JSON jest często preferowany nad XML w kontekście API webowych ze względu na mniejszą rozwlekłość, prostszą składnię i lepsze dopasowanie do struktur danych w JavaScript. XML nadal ma swoje zastosowania, szczególnie w systemach korporacyjnych, dokumentach i tam, gdzie wymagane są schematy (XSD) czy transformacje (XSLT).

Jak obsłużyć bardzo duże pliki JSON w PHP?

json_decode() wczytuje cały string JSON do pamięci. Dla bardzo dużych plików JSON, które nie mieszczą się w pamięci, potrzebne są techniki strumieniowego parsowania. Można użyć bibliotek takich jak JSON Machine lub salsify/jsonstreamingparser, które pozwalają przetwarzać JSON fragment po fragmencie.

Co jeśli mój string JSON nie jest w UTF-8?

Funkcje JSON w PHP oczekują stringów w UTF-8. Jeśli masz dane w innym kodowaniu (np. ISO-8859-2, Windows-1250), musisz je najpierw przekonwertować na UTF-8, np. za pomocą funkcji mb_convert_encoding() lub iconv(), przed przekazaniem do json_encode() lub json_decode().

Czy mogę przechowywać komentarze w pliku JSON?

Standard JSON nie dopuszcza komentarzy. Jeśli potrzebujesz komentarzy w pliku konfiguracyjnym, rozważ użycie formatu JSONC (JSON with Comments) i odpowiedniego parsera, lub innego formatu jak YAML, który natywnie wspiera komentarze.

Jak przekształcić obiekt PHP na JSON, włączając prywatne właściwości?

Najlepszym sposobem jest zaimplementowanie interfejsu JsonSerializable w klasie obiektu. Metoda jsonSerialize() tej klasy musi zwrócić dane, które mają być zserializowane przez json_encode(). W tej metodzie masz pełną kontrolę nad tym, które właściwości (publiczne, prywatne, chronione) i w jakiej formie zostaną uwzględnione.

Czy json_decode(..., true) zawsze jest lepsze niż dekodowanie do obiektu?

To zależy od preferencji i kontekstu. Praca z tablicami asocjacyjnymi ($assoc = true) jest często bardziej naturalna dla programistów PHP przyzwyczajonych do tablic. Obiekty stdClass mogą być nieco bardziej wydajne w niektórych przypadkach i są bliższe oryginalnej koncepcji JSON jako notacji obiektowej. Wybór zależy od stylu kodowania i wymagań projektu.

Co oznacza opcja JSON_NUMERIC_CHECK w json_encode()?

Ta opcja próbuje automatycznie przekonwertować stringi zawierające liczby na typy numeryczne w wynikowym JSON. Należy jej używać ostrożnie, ponieważ może prowadzić do nieoczekiwanych rezultatów lub utraty danych, jeśli string nie jest poprawną liczbą lub reprezentuje np. numer telefonu z wiodącymi zerami, który nie powinien być traktowany jako liczba.