1. Lectura de JSON en Map y List
Normalmente, cuando trabajamos con JSON, conocemos de antemano su estructura y podemos describirla como una clase de Java. Pero en la práctica no todo es tan predecible: los campos pueden aparecer y desaparecer, y la anidación puede cambiar. En esos casos resulta mucho más cómodo operar con estructuras de datos universales — Map, List — o con árboles JSON.
Crear un modelo de Java para cada variación es lento y frágil. Las colecciones universales y los árboles permiten extraer de forma flexible solo las partes necesarias sin atarse a un esquema fijo.
Deserialización de un objeto JSON en Map
Ejemplo de JSON:
{
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"active": true
}
Deserialización en 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);
// Salida: {id=123, name=Alice, email=alice@example.com, active=true}
}
}
Ahora puedes acceder a cualquier campo como a un elemento del mapa:
System.out.println(data.get("name")); // Alice
Deserialización de un array de objetos en List
Array JSON:
[
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]
Deserialización:
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"));
}
// Salida:
// Alice
// Bob
}
}
Matiz importante: al leer en estructuras universales, todos los objetos anidados se convierten en Map y los arrays en List. Para estructuras complejas tendrás que realizar conversiones de tipo con cuidado y verificarlas:
Object items = data.get("items");
if (items instanceof List) {
List<?> itemList = (List<?>) items;
// ...
}
2. JsonNode: árbol JSON en Jackson
Es cómodo trabajar con Map/List, pero no es seguro: puedes equivocarte con el tipo o pasar por alto una anidación. Jackson ofrece una herramienta más potente — la clase JsonNode. Es un árbol universal donde cada nodo es un objeto, un array, un valor o null.
Obtener 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 — es la raíz del árbol JSON
}
}
Acceso a los campos
int id = root.get("id").asInt(); // 123
String name = root.get("name").asText(); // Alice
JsonNode tags = root.get("tags"); // array
System.out.println("Nombre: " + name);
Recorrido de un array
for (JsonNode tag : tags) {
System.out.println(tag.asText());
}
// Salida:
// java
// json
Objetos anidados
Ejemplo de JSON complejo:
{
"user": {
"id": 1,
"profile": {
"nickname": "java_guru",
"age": 25
}
}
}
Extracción de un valor anidado:
JsonNode profile = root.get("user").get("profile");
String nickname = profile.get("nickname").asText();
System.out.println(nickname); // java_guru
Acceso seguro: get vs path
- get("clave") — si la clave no existe, devolverá null. Cualquier llamada como asText() sobre null provocará un NullPointerException.
- path("clave") — si la clave no existe, devolverá un nodo «vacío», y asText() dará "", asInt() — 0.
String phone = root.path("phone").asText(); // "" si no existe el campo
Comprobación del tipo de nodo
if (root.has("tags") && root.get("tags").isArray()) {
for (JsonNode tag : root.get("tags")) {
// ...
}
}
Modificación de JsonNode
JsonNode es inmutable. Para crear/modificar el árbol usa 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();
// Creación de un nuevo objeto
ObjectNode obj = mapper.createObjectNode();
obj.put("id", 10);
obj.put("name", "Bob");
// Añadir un array
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. Trabajo con Gson: JsonElement, JsonObject, JsonArray
Jackson no es el único actor. Gson también ofrece una API cómoda para JSON dinámico: JsonElement, JsonObject, JsonArray.
Análisis en 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);
Acceso a los campos
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());
}
Anidación y seguridad
if (obj.has("email")) {
String email = obj.get("email").getAsString();
}
Trabajo con arrays
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());
}
Modificación
En Gson los objetos son mutables: se pueden añadir y eliminar campos:
import com.google.gson.JsonObject;
JsonObject newObj = new JsonObject();
newObj.addProperty("id", 42);
newObj.add("tags", tags);
System.out.println(newObj.toString());
4. Práctica: extraer datos de un JSON desconocido
Tarea: Supongamos que tenemos un JSON de configuración cuya estructura puede cambiar:
{
"service": "mail",
"enabled": true,
"params": {
"host": "smtp.example.com",
"port": 587
}
}
Extraer host y port con 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
Lo mismo con 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. Errores típicos y matices
Error n.º 1: conversión de tipos errónea. Si esperas una cadena pero el campo contiene un número o null, llamar a asText() o getAsString() puede dar un resultado inesperado o lanzar una excepción. Por ejemplo, si el campo falta, root.get("foo").asText() provocará un NullPointerException.
Error n.º 2: no comprobar null. Especialmente con accesos anidados: root.get("params").get("host"). Si params no existe — habrá NPE. En Jackson usa path(); en Gson, comprueba la existencia con has y el tipo con isJsonObject()/isJsonArray().
Error n.º 3: confusión con los tipos de nodos. Si un campo es un array y lo tratas como un objeto, obtendrás un error. Comprueba los tipos: Jackson — isArray()/isObject(), Gson — isJsonArray()/isJsonObject().
Error n.º 4: pérdida de información de tipos al trabajar con Map/List. Al deserializar en Map<String, Object>, las estructuras anidadas se convierten en una cascada de Map/List, lo que complica la navegación y conduce a numerosas conversiones de tipo y comprobaciones con instanceof.
Error n.º 5: no entender la (in)mutabilidad del árbol. En Jackson, JsonNode es inmutable y las modificaciones deben hacerse mediante ObjectNode y ArrayNode. En Gson, los objetos son mutables; recuerda que cambiar un nodo afecta a todas las referencias a él en el árbol actual.
GO TO FULL VERSION