Serializar y Deserializar Datos
Cómo serializar y deserializar datos en JSON, XML y YAML en Python, Java y JavaScript.
Visión General
La serialización convierte un objeto en memoria a un formato almacenable y transmisible; la deserialización revierte el proceso y reconstruye el objeto desde bytes o texto. Las encontrarás en todas partes: APIs, caché, colas de mensajes, archivos de configuración, persistencia de sesiones. Justo abajo tienes snippets funcionales de JSON, XML y YAML en Python, JavaScript y Java. Si necesitas pares clave-valor planos en lugar de un flujo de bytes serializado, consulta Aplanar y Reconstruir Objetos.
Cuándo Usar
Recurre a la serialización cuando necesites:
- Enviar payloads por APIs HTTP o un broker de mensajes
- Persistir estado de la app en disco o en una capa de caché
- Mover una configuración entre formatos (JSON ↔ YAML ↔ XML)
- Pasar mensajes tipados entre servicios que no comparten codebase
Solución
Python
import json
# Serializar (objeto -> string JSON)
data = {'name': 'Alice', 'age': 30, 'active': True}
json_str = json.dumps(data, indent=2)
print(json_str)
# Deserializar (string JSON -> objeto)
parsed = json.loads(json_str)
print(parsed['name'])
# Serialización YAML con PyYAML
import yaml
yaml_str = yaml.safe_dump(data, default_flow_style=False)
parsed_yaml = yaml.safe_load(yaml_str)
JavaScript
// JSON es nativo en JavaScript
const data = { name: 'Alice', age: 30, active: true };
// Serializar
const jsonStr = JSON.stringify(data, null, 2);
console.log(jsonStr);
// Deserializar
const parsed = JSON.parse(jsonStr);
console.log(parsed.name);
// Serialización YAML con js-yaml
// npm install js-yaml
import yaml from 'js-yaml';
const yamlStr = yaml.dump(data);
const parsedYaml = yaml.load(yamlStr);
Java
// Jackson es el estándar para JSON en Java
// Maven: com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerializationDemo {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
User user = new User("Alice", 30, true);
// Serializar
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(json);
// Deserializar
User parsed = mapper.readValue(json, User.class);
System.out.println(parsed.getName());
}
}
class User {
private String name;
private int age;
private boolean active;
public User() {}
public User(String name, int age, boolean active) {
this.name = name; this.age = age; this.active = active;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
}
Explicación
JSON es el formato de intercambio dominante por su simplicidad y soporte nativo en JavaScript y lenguajes modernos. Mapea limpiamente a diccionarios/objetos, arrays, strings, números, booleanos y null. XML sigue siendo relevante en servicios SOAP empresariales, archivos de configuración (Spring, Android) y flujos centrados en documentos. YAML se prefiere para configuraciones editadas por humanos porque soporta comentarios y anidamiento complejo con sintaxis mínima.
Internamente, Jackson aplica reflection sobre tu POJO — vincula los campos JSON mediante getters/setters o, si lo permites, campos públicos. El módulo json de Python trabaja con cualquier tipo serializable a JSON (dicts, listas, primitivos). JSON.stringify de JavaScript maneja mal los ciclos (lanza TypeError) a menos que se provea un replacer.
Variantes
| Tecnología | Formato | Librería | Enfoque | Notas |
|---|---|---|---|---|
| Python | JSON | json (stdlib) | dumps() / loads() | Maneja tipos básicos, soporte de encoder personalizado |
| Python | YAML | PyYAML | safe_dump() / safe_load() | Soporta tags personalizados y anchors |
| Python | XML | xml.etree.ElementTree | tostring() / fromstring() | Librería estándar, sin validación de esquema |
| JavaScript | JSON | Nativo | JSON.stringify() / JSON.parse() | Cero dependencias, soporta reviver/replacer |
| JavaScript | YAML | js-yaml | dump() / load() | Rápido, seguro por defecto |
| Java | JSON | Jackson | writeValueAsString() / readValue() | Binding a POJO, streaming, modelo de árbol |
| Java | XML | JAXB / Jackson XML | Guiado por anotaciones | JAXB está deprecado; preferir Jackson XML |
Los formatos de texto (JSON, XML, YAML) son autodescriptivos y depurables; los formatos binarios (MessagePack, Protocol Buffers, Avro) son más pequeños y rápidos de parsear pero requieren esquemas compartidos y herramientas para inspeccionarlos. Elige formatos schema-first cuando servicios de distintos equipos intercambian datos — el contrato vive fuera del código — y JSON sin esquema cuando la flexibilidad importa más que el tamaño del payload.
Lo que funciona
- Usa
safe_loadpara YAML en contextos no confiables para prevenir ejecución de código arbitrario - Valida contra un esquema después de deserializar para detectar errores estructurales antes de la lógica de negocio — consulta Validar JSON Schema
- Maneja referencias circulares explícitamente en
JSON.stringifycon un replacer o una librería comoflatted - Versiona tus datos serializados agregando un campo
schema_versionpara evolución compatible hacia atrás - Prefiere JSON para APIs y YAML para configuraciones; evita XML salvo que integres con sistemas legacy
Errores Comunes
- No manejar
undefineden JavaScript:JSON.stringifydescarta silenciosamente valoresundefined, funciones y propiedades con claveSymbol. Un campo presente en memoria puede desaparecer del payload sin ningún error. - Olvidar constructores por defecto en Java: Jackson requiere un constructor sin argumentos para la deserialización
- Usar
floatpara valores monetarios: la serialización puede introducir errores de precisión; usaDecimal/BigDecimal - No configurar headers content-type: las APIs deberían enviar
application/json, notext/plain - Ignorar problemas de encoding: especifica siempre UTF-8 al leer y escribir archivos de texto serializados
- Cargar YAML con un loader inseguro:
yaml.load()en PyYAML puede instanciar objetos arbitrarios; usasafe_loadcon cualquier entrada que no controles - Asumir que JSON preserva el orden de claves y los números exactos: los objetos no tienen orden y JavaScript pierde precisión entera más allá de 2^53 — usa strings o
BigIntpara IDs grandes
Cuándo No Usar Este Enfoque
- Payloads binarios en enlaces internos: si el tamaño del payload es el cuello de botella entre tus propios servicios, un formato binario como MessagePack o Protobuf supera a los formatos de texto.
- Contratos fuertemente tipados entre equipos: cuando productores y consumidores evolucionan de forma independiente, el JSON sin esquema diverge en silencio; adopta una política de evolución de esquemas y contract tests.
- Streaming de payloads de varios gigabytes: cargar el documento completo en memoria falla a escala; cambia a parsers de streaming (Jackson
JsonParser,ijsonen Python) o a un formato columnar. - Persistir objetos sensibles directamente: serializar una entidad vuelca todo el grafo de objetos, incluidos campos que quizá no quieras almacenar; serializa DTOs dedicados en su lugar.
Consideraciones de Seguridad
- Los formatos de objetos nativos sobre entrada no confiable son RCE:
pickle.loads(), la serialización nativa de Java yunserialize()de PHP ejecutan lo que el flujo de bytes les ordene. Todo lo que cruce tu frontera de confianza pasa por un formato de solo datos — JSON, no pickle. - Prototype pollution vía merge de JSON: fusionar JSON del usuario que contiene claves
__proto__oconstructorpuede contaminar los prototipos de objetos JavaScript. Elimina las claves peligrosas o construye el resultado sobre un objeto sin prototipo. - Loaders YAML inseguros:
yaml.load()(PyYAML) y loaders inseguros equivalentes construyen objetos arbitrarios desde el documento, así que manténsafe_load/SafeLoaderen todo lo que cruce tu frontera de confianza. - XXE en parsers XML: una declaración de entidad externa manipulada permite al parser leer archivos locales o golpear endpoints internos. Desactiva DTDs y entidades externas —
FEATURE_SECURE_PROCESSINGen Java,defusedxmlen Python. - Ambigüedad de claves duplicadas: pasa
{"role": "user", "role": "admin"}por dos parsers distintos y puedes obtener dos resultados distintos — la mayoría toma el último valor, unos pocos el primero. Esa brecha es explotable: un WAF leyendo el texto crudo puede ver una cosa mientras tu parser ve otra. - Límites de profundidad y tamaño: un documento anidado 10 000 niveles, o un string de cientos de MB, desbordará la pila o agotará la memoria. Limita ambos en el borde del sistema.
Consideraciones de Rendimiento
- stdlib vs parsers optimizados: el
jsonde Python cubre la mayoría de cargas;orjsoncorre varias veces más rápido en servicios de alto rendimiento y te devuelve bytes. En JavaScript,JSON.parse/JSON.stringifyya van por la vía rápida de V8. - El pretty-printing cuesta tamaño:
indent=2infla los payloads aproximadamente un 20–40%. Úsalo para configuraciones que leen humanos, no para formatos de trasmisión. - Binding vs streaming: el
ObjectMapperde Jackson reserva todo el grafo de objetos; la API de streamingJsonParserlee documentos muy grandes con memoria casi constante. - Formatos binarios cuando el tamaño importa: los payloads de MessagePack y Protobuf suelen ser varias veces más pequeños que el JSON equivalente y se parsean más rápido — la contrapartida es perder legibilidad humana y necesitar esquemas compartidos.
- Cachea bytes serializados: en rutas calientes que sirven payloads inmutables, serializa una vez y guarda los bytes — re-serializar el mismo objeto por petición es CPU desperdiciada.
Estrategia de Testing
- Tests de round-trip: serializa, deserializa y verifica igualdad estructural. Detecta coerción silenciosa de tipos — las fechas se vuelven strings, las tuplas listas,
Decimalse vuelvefloat. - Corpus de casos borde:
{},null, arrays muy anidados, strings Unicode (emoji, RTL) y enteros por encima de 2^53 — JavaScript los redondea en silencio. - Cobertura de tipos: recorre tu esquema y verifica que cada tipo de campo sobrevive el round-trip.
datetime,Decimal,bytesy enums necesitan encoders explícitos en eljsonde Python o serializers personalizados en Jackson. - Entrada adversarial: JSON muy anidado (miles de niveles), strings enormes, decenas de miles de claves y claves duplicadas para sondear los límites del parser.
- Contract tests entre versiones: un fixture por cada versión publicada del esquema, todos deserializados en CI. Los cambios incompatibles afloran antes del despliegue en lugar de en producción.
Manejo de Errores y Recuperación
- Entrada malformada: captura los errores de parseo en el borde y devuelve un error estructurado con la ubicación del problema (
json.JSONDecodeErroren Python,SyntaxErroren JS,JsonParseExceptionen Jackson) — nunca un 500 genérico. - Inconsistencias de encoding: decodifica explícitamente como UTF-8 y expón
UnicodeDecodeErrorcuanto antes. Un archivo guardado como UTF-16 o con BOM falla de formas confusas cuando se lee como UTF-8 plano. - Desajuste de esquema/versión: un payload sin
schema_version, o con una más nueva de lo soportado, merece un error claro y una ruta de migración — no campos descartados en silencio. - Deserialización parcial: un payload puede ser JSON válido y aun así carecer de campos requeridos. Valida después del parseo, no en su lugar.
Solución de Problemas
Object of type X is not JSON serializable(Python): el tipo no tiene encoder por defecto — pasa un callabledefault=o crea una subclase deJSONEncoderparadatetime,Decimal,setybytes.TypeError: Converting circular structure to JSON(JavaScript): el grafo de objetos tiene ciclos — usa un replacer que omita las referencias hacia atrás o una librería comoflatted.- Jackson falla con campos desconocidos: agrega los campos al POJO o configura
FAIL_ON_UNKNOWN_PROPERTIESenfalsepara compatibilidad futura. - Acentos corruptos o caracteres
Ãen la salida: el archivo se escribió en un encoding y se leyó en otro. Especifica UTF-8 en ambos extremos y maneja el BOM explícitamente. - Las fechas vuelven como strings: JSON no tiene tipo fecha. Acuerda strings ISO-8601 y parséalos explícitamente, o cada extremo terminará con un tipo distinto.
Lectura Adicional
Preguntas frecuentes
¿Qué formato de serialización debería elegir para microservicios?
Dentro de tu propia flota, Protobuf o MessagePack gana en tamaño de payload y contratos tipados. Para APIs públicas y todo lo que lean humanos, JSON sigue siendo la elección correcta — autodescriptivo y entendido en todas partes.
¿Cómo manejo la serialización de objetos personalizados en Python?
Dos opciones: crea una subclase de JSONEncoder, o pásale a json.dumps() un callable default= que devuelva un dict serializable para tu tipo. En el camino de vuelta, un object_hook pasado a json.loads() convierte esos dicts otra vez en tus objetos.
¿Puedo serializar objetos Java sin getters y setters?
Sí. Jackson puede serializar campos públicos directamente si se configura con ObjectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY). Aun así, usar getters/setters es la convención estándar de Java y mantiene la encapsulación. Como alternativa, usa records (Java 14+) que generan constructores canónicos y métodos de acceso automáticamente.
¿Por qué JSON.stringify descarta campos sin avisar en JavaScript?
Los valores undefined, las funciones y las propiedades con clave Symbol se eliminan en lugar de serializarse. Los Date se convierten en strings ISO y Map/Set se serializan como {}. Preprocesa el objeto antes o compara la lista de campos con la original después de stringify.
¿Es seguro usar pickle con datos de una fuente no confiable?
No. Un pickle no son datos, es un flujo de instrucciones — pickle.loads() ejecuta los opcodes que contenga, así que un pickle manipulado por un atacante equivale a una shell en tu máquina. Solo deserializa pickles que generaste tú; lo externo va por JSON.
Recursos Relacionados
Parsear JSON en Python, JavaScript y Java
Parsea cadenas JSON a objetos nativos en Python, JavaScript y Java — con manejo de errores, streaming para archivos grandes y validación de esquema.
RecipeAnalizar Archivos XML
Cómo analizar documentos XML en Python, Java y JavaScript con ejemplos de código prácticos.
RecipeAnalizar Archivos YAML
Cómo analizar archivos de configuración YAML en Python, Java y JavaScript.
RecipeValidar JSON Schema
Cómo validar datos JSON contra schemas en Python, Java y JavaScript.
DocPolítica de Evolución de Schemas (Avro, Protobuf, JSON)
Política reutilizable para evolucionar esquemas de mensajes en sistemas orientados a eventos: reglas de compatibilidad, schema registry, versionado, procedimiento de cambios rupturistas, checklist de migración y enforcement en CI con Avro, Protobuf y JSON Schema.
PatternPatrón Schema Registry Evolution
Cómo manejar schema versions para streaming pipelines con un schema registry. Cubre Avro, backward compatibility, forward compatibility, y consumer migration.