1. Читання JSON у Map і List
Зазвичай, коли ми працюємо з JSON, ми заздалегідь знаємо його структуру й можемо описати її у вигляді Java‑класу. Але на практиці все буває не так передбачувано: поля можуть з’являтися й зникати, вкладеність — змінюватися. У таких випадках набагато зручніше оперувати універсальними структурами даних — Map, List — або деревами JSON.
Створювати Java‑модель під кожну варіацію — довго й крихко. Універсальні колекції та дерева дозволяють гнучко діставати лише потрібні частини, не прив’язуючись до фіксованої схеми.
Десеріалізація JSON‑об’єкта в Map
Приклад JSON:
{
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"active": true
}
Десеріалізація у Map:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
String json = "{\"id\":123,\"name\":\"Alice\",\"email\":\"alice@example.com\",\"active\":true}";
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> data = mapper.readValue(json, Map.class);
System.out.println(data);
// Виведення: {id=123, name=Alice, email=alice@example.com, active=true}
}
}
Тепер ви можете звертатися до будь‑якого поля як до елемента словника:
System.out.println(data.get("name")); // Alice
Десеріалізація масиву об’єктів у List
JSON‑масив:
[
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]
Десеріалізація:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;
public class UsersReadExample {
public static void main(String[] args) throws Exception {
String json = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";
ObjectMapper mapper = new ObjectMapper();
List<Map<String, Object>> users = mapper.readValue(json, List.class);
for (Map<String, Object> user : users) {
System.out.println(user.get("name"));
}
// Виведення:
// Alice
// Bob
}
}
Важливий нюанс: під час читання в універсальні структури всі вкладені об’єкти перетворюються на Map, а масиви — на List. Для складних структур доведеться обережно приводити типи й перевіряти їх:
Object items = data.get("items");
if (items instanceof List) {
List<?> itemList = (List<?>) items;
// ...
}
2. JsonNode: дерево JSON у Jackson
Працювати з Map/List зручно, але небезпечно: можна помилитися з типом або пропустити вкладеність. Jackson пропонує потужніший інструмент — клас JsonNode. Це універсальне дерево, де кожен вузол — об’єкт, масив, значення або null.
Отримання JsonNode
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
public class JsonNodeStart {
public static void main(String[] args) throws Exception {
String json = "{\"id\":123,\"name\":\"Alice\",\"tags\":[\"java\",\"json\"]}";
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
// root — це корінь JSON‑дерева
}
}
Доступ до полів
int id = root.get("id").asInt(); // 123
String name = root.get("name").asText(); // Alice
JsonNode tags = root.get("tags"); // масив
System.out.println("Ім'я: " + name);
Обхід масиву
for (JsonNode tag : tags) {
System.out.println(tag.asText());
}
// Вивід:
// java
// json
Вкладені об’єкти
Приклад складного JSON:
{
"user": {
"id": 1,
"profile": {
"nickname": "java_guru",
"age": 25
}
}
}
Добування вкладеного значення:
JsonNode profile = root.get("user").get("profile");
String nickname = profile.get("nickname").asText();
System.out.println(nickname); // java_guru
Безпечний доступ: get vs path
- get("ключ") — якщо ключа немає, поверне null. Будь‑який виклик на кшталт asText() на null спричинить NullPointerException.
- path("ключ") — якщо ключа немає, поверне «порожній» вузол, і asText() дасть "", asInt() — 0.
String phone = root.path("phone").asText(); // "" якщо поля немає
Перевірка типу вузла
if (root.has("tags") && root.get("tags").isArray()) {
for (JsonNode tag : root.get("tags")) {
// ...
}
}
Модифікація JsonNode
JsonNode — незмінний. Для створення/зміни дерева використовуйте ObjectNode/ArrayNode.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
ObjectMapper mapper = new ObjectMapper();
// Створення нового об’єкта
ObjectNode obj = mapper.createObjectNode();
obj.put("id", 10);
obj.put("name", "Bob");
// Додавання масиву
ArrayNode arr = mapper.createArrayNode();
arr.add("Java").add("JSON");
obj.set("tags", arr);
System.out.println(obj.toPrettyString());
/*
{
"id" : 10,
"name" : "Bob",
"tags" : [ "Java", "JSON" ]
}
*/
3. Робота з Gson: JsonElement, JsonObject, JsonArray
Jackson — не єдиний гравець. Gson теж надає зручне API для динамічного JSON: JsonElement, JsonObject, JsonArray.
Розбір у JsonElement
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
String json = "{\"id\":123,\"name\":\"Alice\",\"tags\":[\"java\",\"json\"]}";
JsonElement root = JsonParser.parseString(json);
Доступ до полів
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
JsonObject obj = root.getAsJsonObject();
int id = obj.get("id").getAsInt();
String name = obj.get("name").getAsString();
JsonArray tags = obj.getAsJsonArray("tags");
for (JsonElement tag : tags) {
System.out.println(tag.getAsString());
}
Вкладеність і безпека
if (obj.has("email")) {
String email = obj.get("email").getAsString();
}
Робота з масивами
String arrJson = "[{\"id\":1},{\"id\":2}]";
JsonArray arr = JsonParser.parseString(arrJson).getAsJsonArray();
for (JsonElement el : arr) {
JsonObject item = el.getAsJsonObject();
System.out.println(item.get("id").getAsInt());
}
Модифікація
У Gson об’єкти змінні — можна додавати й видаляти поля:
import com.google.gson.JsonObject;
JsonObject newObj = new JsonObject();
newObj.addProperty("id", 42);
newObj.add("tags", tags);
System.out.println(newObj.toString());
4. Практика: добування даних з невідомого JSON
Завдання: Нехай у нас є JSON‑конфігурація, де структура може змінюватися:
{
"service": "mail",
"enabled": true,
"params": {
"host": "smtp.example.com",
"port": 587
}
}
Добути host і port за допомогою Jackson:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode params = root.path("params");
String host = params.path("host").asText();
int port = params.path("port").asInt();
System.out.println(host + ":" + port); // smtp.example.com:587
Те саме з Gson:
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
JsonObject rootObj = JsonParser.parseString(json).getAsJsonObject();
JsonObject params = rootObj.getAsJsonObject("params");
String host = params.get("host").getAsString();
int port = params.get("port").getAsInt();
System.out.println(host + ":" + port);
5. Типові помилки та нюанси
Помилка №1: Помилкове приведення типів. Якщо ви очікуєте рядок, а в полі число або null, виклик asText() чи getAsString() дасть неочікуваний результат або виняток. Наприклад, за відсутності поля root.get("foo").asText() спричинить NullPointerException.
Помилка №2: Відсутність перевірки на null. Особливо під час вкладених звернень: root.get("params").get("host"). Якщо params відсутній — буде NPE. У Jackson використовуйте path(), у Gson — перевіряйте наявність через has і тип через isJsonObject()/isJsonArray().
Помилка №3: Плутанина з типами вузлів. Якщо поле — масив, а ви звертаєтеся до нього як до об’єкта — отримаєте помилку. Перевіряйте типи: Jackson — isArray()/isObject(), Gson — isJsonArray()/isJsonObject().
Помилка №4: Втрата інформації про типи під час роботи з Map/List. Під час десеріалізації в Map<String, Object> вкладені структури стають каскадом Map/List, що ускладнює навігацію та призводить до численних приведень типів і перевірок instanceof.
Помилка №5: Нерозуміння (не)змінюваності дерева. У Jackson JsonNode незмінний, а змінювати слід через ObjectNode і ArrayNode. У Gson об’єкти змінні; пам’ятайте, що зміна вузла впливає на всі посилання на нього в поточному дереві.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ