El lenguaje de marcado de hipertexto para páginas web HTML y el formato de datos JSON tienen una característica en común: ambos ayudan a estructurar la información, aunque en el desarrollo web se utilizan para tareas diferentes. HTML es necesario para mostrar el marcado de los sitios web, mientras que JSON se utiliza para intercambiar datos con el servidor.
Es decir, al enviar información desde el servidor en formato JSON, la hacemos independiente del formato de visualización en el dispositivo del usuario: puede desplazarse por las pantallas de una aplicación móvil nativa (donde no se utiliza HTML), solicitar datos desde una utilidad de consola o verlos en un navegador. Además, para la visualización en un navegador a menudo se necesita una lógica JavaScript “de varios niveles”, que simplemente no se puede empaquetar en el HTML generado por el servidor.
En general, las bibliotecas para trabajar con JSON existen en casi cualquier lenguaje de programación moderno, es decir, es un formato universal. Sin embargo, la propia abreviatura JSON es inicialmente una abreviatura de JavaScript Object Notation, por lo que en JS existen métodos integrados para trabajar con JSON. Además, el tipo de datos en este último es casi idéntico a la sintaxis de los objetos en JavaScript. Es decir, JavaScript admite perfectamente su formato “hijo”.
¿Dónde se encuentra este JSON tuyo?
Así pues, muchos desarrolladores web prefieren emitir, tras una solicitud del frontend, código HTML empaquetado en JSON, y ya en el navegador convertirlo en un marcado HTML normal. Así funciona nuestro sitio web — Skillbox Media (lo analizamos en un artículo sobre el análisis de datos).
Respuesta JSON al cargar artículos en la página Skillbox Media «Código» (después de presionar el botón «Mostrar más»)
El mismo método de carga de artículos adicionales en las secciones de países individuales también se implementa en el sitio web de la organización “Reporteros sin Fronteras”. Seleccionemos un país al azar — que sea Italia. Al hacer clic en el botón “Mostrar más publicaciones”, se cargan tres artículos más antiguos. Si haces clic en el enlace del botón, dentro del elemento <textarea> veremos una inserción JSON con el código de los artículos:
Respuesta JSON al cargar artículos en las secciones de países del sitio web de la organización “Reporteros sin Fronteras” (primera parte del trabajo)
Captura de pantalla: Skillbox Media
Respuesta JSON al cargar artículos en las secciones de países del sitio web de la organización “Reporteros sin Fronteras” (segunda parte del trabajo)
Captura de pantalla: Skillbox Media
Convertir HTML a JSON no es difícil si conoces las características de ambos formatos. Primero, analizaremos brevemente estas características, y luego escribiremos una pequeña aplicación que convertirá un formato en otro.
Brevemente sobre la diferencia entre HTML y JSON
Recordemos que el marcado HTML de un sitio web consta de elementos que, por regla general, constan de tres partes: etiqueta de apertura, etiqueta de cierre y contenido:
<p>HTML vs. JSON</p>Dentro de la etiqueta de apertura pueden ubicarse varios atributos en el formato attribute="value":
<p class="zag">HTML vs. JSON</p>JSON está organizado de manera diferente: consta de un conjunto de pares “clave”: “valor”. Las claves y los valores están entre comillas, después de la clave hay dos puntos, los pares están separados por comas y están empaquetados dentro de paréntesis redondos.
Como valores en el marcado JSON pueden usarse números, cadenas, valores booleanos, objetos, matrices y nulo (nos será útil saber esto más adelante, ya que crearemos un ejemplo con objetos y matrices).
{
"key1": "value",
"key2": "value"
}El último par no está separado por una coma: las comas finales (comas finales) están prohibidas en JSON.
Parece que las diferencias son notables, pero esto solo a primera vista: en realidad, representar el código HTML en formato JSON es relativamente sencillo, observa:
<p>La ornitología es la ciencia de las aves.</p>{
"p": "La ornitología es la ciencia de las aves."
}Cualquier elemento HTML se convierte fácilmente en un par JSON “clave”: “valor”, donde la clave es el nombre del elemento y el valor es su contenido de texto o un elemento anidado del siguiente nivel.
Ahora intentemos hacer algo similar con JavaScript en el ejemplo de una página web.
Realizamos la conversión
El proceso de empaquetado de datos en JSON se llama serialización, y el proceso inverso, deserialización. Puedes convertir el marcado HTML en JSON de dos maneras:
- Convertir el marcado “directamente”, metiendo todo el código HTML en un par “clave”: “valor” — es decir, como valor de la clave
<html> - Escribir una variante JSON más compleja para que la estructura de la cadena JSON corresponda completamente a la estructura del documento HTML.
La elección del método dependerá de tus tareas. Nosotros, con fines educativos, analizaremos el método más complejo, el segundo — esto nos permitirá probar las capacidades de JavaScript y preservar la estructura de la página. Tomemos como ejemplo el siguiente código:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Conversión de HTML a JSON</title>
</head>
<body>
<p class="myclass" style="margin: 1px;">Algún párrafo.</p>
<div>
<p><span>Otro párrafo (elemento de tercer nivel)</span></p>
<span>Otro párrafo (elemento de segundo nivel)</span>
<span><a href="#">Algún enlace (elemento de tercer nivel).</a></span>
<img src="images/test.png" alt="Imagen de prueba">
</div>
<script src="script.js"></script>
</body>
</html>Aquí están todos los componentes básicos de la plantilla de una página HTML: elementos de servicio y contenido en el cuerpo de la página. Algunos elementos tienen atributos y elementos anidados, lo que también debe tenerse en cuenta al realizar la conversión.
Volvamos a la situación típica: el marco de la página es estático, y al cargar desde el servidor solo cambia el cuerpo, que es lo que debe alimentarse al conversor JSON. Por lo tanto, nos interesa el contenido del elemento <body>:
<p class="myclass" style="margin: 1px;">Algún párrafo.</p>
<div>
<p><span>Otro párrafo (elemento de tercer nivel)</span></p>
<span>Otro párrafo (elemento de segundo nivel)</span>
<span><a href="#">Algún enlace (elemento de tercer nivel).</a></span>
<img src="images/test.png" alt="Imagen de prueba">
</div>
<script src="script.js"></script>Ahora hay varios párrafos arbitrarios y una imagen en la página. Nuestro conversor de entrenamiento será casi universal y podrá empaquetar en JSON estos y cualquier otro elemento HTML en el cuerpo — hasta el tercer nivel de anidamiento.
El algoritmo será el siguiente:
- Crearemos un objeto JavaScript para registrar los elementos HTML, sus atributos y su contenido.
- Registraremos en el objeto todos los elementos uno por uno.
- Transformaremos el objeto en formato JSON.
Un poco más abajo analizaremos estos pasos en detalle, pero primero debemos crear dos archivos:
- El primero lo llamaremos “HTML a JSON.html” (en el enlace — el contenido del archivo en el servicio Pastebin) y colocaremos en él la propia página para la transformación.
- El segundo se llamará “script.js” (en el enlace — el contenido del archivo en el servicio Pastebin) — en él colocaremos el conversor de página a JSON escrito en JavaScript.
El script se llamará después de cargar nuestra página, por lo que colocaremos en el cuerpo del archivo HTML el elemento <script> con un enlace al archivo JS (ver la primera inserción de código).
Ahora veamos paso a paso cómo funcionará nuestro conversor en el archivo script.js.
function convertHTMLtoJSON() {
// Aquí estará el conversor
};
// Mostramos el resultado un segundo después de cargar la página original
setTimeout(convertHTMLtoJSON, 1000);Primero, declaramos una función con un nombre claro — “conversor de HTML a JSON” (convertHTMLtoJSON). Su cuerpo contendrá el conversor. Después de declarar la función, colocaremos su llamada con un retraso de un segundo usando el método incorporado setTimeout(): de esta manera, después de abrir el archivo HTML en el navegador, primero veremos la página original, y luego se borrará y se reemplazará con una cadena JSON.
Ahora nos ocuparemos del cuerpo de la función creada y, por estética, en cada paso de su funcionamiento mostraremos notificaciones claras en la consola (la captura de pantalla con ellas estará más abajo). Empezaremos con esto:
// Limpieza de la consola para repetir el lanzamiento del ejemplo
console.clear();
// Mostrar en la consola el nombre del programa
console.log('= CONVERTIDOR HTML A JSON =');
// Notificación en la consola sobre el inicio del trabajo del convertidor
console.log(' INICIADA LA SERIALIZACIÓN DEL CÓDIGO HTML.');Los resultados de la búsqueda de elementos, sus atributos y su contenido también irán acompañados de notificaciones, para que no nos perdamos nada importante.
Ya hemos mencionado que el formato JSON se parece a los objetos en JavaScript. Por eso, siguiendo el algoritmo, crearemos un objeto y registraremos en él los elementos HTML.
let objectToStringify = new Object(); // Creamos un objeto para registrar los elementos y su contenido (al final, el objeto se convertirá en JSON)Después de crear el objeto, comienza lo principal: el bucle de iteración y registro de los elementos y el contenido en el objeto, y aquí es el momento de hablar sobre lo que queremos obtener concretamente.
La estructura del resultado JSON será la siguiente: el nombre del elemento del primer nivel, luego sus atributos, luego su contenido — elementos anidados o texto. Si el contenido son elementos anidados, entonces dentro estarán sus nombres, atributos y contenido (también elementos anidados o texto). Cada parte de la estructura tendrá un número ordinal — comenzando desde cero — y una indicación del nivel del elemento.
Por ejemplo, el primer elemento en el cuerpo de nuestra página después de la conversión a JSON debe tomar este aspecto:
{
"element0Level1 (número ordinal del primer elemento del primer nivel)": [
{
"element0Level1Name (nombre de este elemento del primer nivel)": "P"
},
{
"element0Level1Attributes (atributos de este elemento del primer nivel)": [
{
"element0Level1attribute0 (primer atributo de este elemento del primer nivel)": [
{
"element0Level1attribute0Name (nombre del primer atributo)": "class"
},
{
"element0Level1attribute0Value (valor del primer atributo)": "myclass"
}
]
},
{
"element0Level1attribute1 (segundo atributo de este elemento del primer nivel)": [
{
"element0Level1attribute1Name (nombre del segundo atributo)": "style"
},
{
"element0Level1attribute1Value (valor del segundo atributo)": "margin: 1px;"
}
]
}
]
},
{
"element0Level1Content (contenido de este elemento del primer nivel, en este caso texto)": "Algún párrafo."
}
],
}Traducido al lenguaje humano: “elemento 0 (es decir, el primero — los programadores tienen su propia matemática) del primer nivel, nombre P, tiene atributos. Atributo 1 class="myclass", atributo 2 style="margin: 1px;". Contiene el texto “Algún párrafo””.
element0Level1 es la clave del primer par "clave": "valor", y todo lo demás es el contenido del valor de este par, que es una matriz de objetos anidados — un objeto con el nombre del elemento, un objeto con todos los atributos y un objeto con el contenido.
Los siguientes elementos del primer nivel se empaquetarán en pares similares, y los elementos del segundo y tercer nivel repetirán la misma estructura en los anidamientos. En resumen, tenemos objetos alternos, matrices anidadas y objetos anidados en ellas.
Ahora que nos hemos imaginado el resultado, analicemos la implementación. El bucle de iteración de los elementos HTML debe desmontarlos en partes — como piezas de Lego. Para encontrar los componentes de los elementos, ayudarán las siguientes propiedades:
tagNameencontrará los nombres de los elementos;attributesdevolverá la colección de atributos del elemento, y las referencias aattributes.nameyattributes.valuedevolverán los nombres y valores de los atributos individuales;- para obtener elementos anidados de cualquier nivel y texto, se necesitarán las propiedades
childrenytextContent, respectivamente.
En cada uno de los niveles de anidamiento de los elementos HTML, primero mediremos su cantidad y luego los iteraremos usando el bucle for.
// Evaluación de los elementos HTML del primer nivel
let bodyElemsLength = document.body.children.length; // Contamos la cantidad de elementos en el bodyAquí y más adelante, contamos la cantidad de elementos HTML o atributos usando la propiedad length.
// Iteración de todos los elementos HTML del primer nivel
for (let e = 0; e < bodyElemsLength; e++) {El nombre de la variable e es solo una abreviatura comprensible de “elemento”. Puedes elegir cualquiera, a tu gusto.
Lo más simple es registrar los nombres de los elementos. Para ello, accedemos a la colección de descendientes del elemento <body> y los iteramos, sustituyendo el siguiente número ordinal del elemento e. Luego accedemos a la propiedad tagName:
// Selección del elemento del primer nivel
let elementLevel1 = document.body.children[e];
// Registro del nombre del elemento del primer nivel
let elementLevel1Name = elementLevel1.tagName; // Selección del nombre del elemento del primer nivel
console.log('ENCONTRADO ELEMENTO DEL PRIMER NIVEL <' + elementLevel1Name + '>'); // Notificación sobre el elemento del primer nivel encontrado
objectToStringify['element' + e + 'Level1'] = [{['element' + e + 'Level1Name']: elementLevel1Name}]; // Registro del nombre del elemento del primer nivel en el objetoAquí y más adelante, construimos nombres de claves comprensibles entre corchetes usando una combinación de texto entre comillas y nombres de las variables correspondientes.
Con la obtención de atributos, la situación se complica: primero hay que comprobar su presencia en el elemento.
// Registro de los atributos del elemento del primer nivel
// Comprobación del elemento del primer nivel para ver si tiene atributos
if (elementLevel1.attributes.length > 0) {La reacción del conversor será diferente dependiendo del resultado de la comprobación. Si se encuentran atributos, hay que contar su cantidad, iterarlos uno por uno y registrarlos bellamente en el lugar correcto.
Para iterar los atributos tenemos un bucle anidado separado, donde la variable a es una abreviatura de la palabra “atributo”.
// Si se encuentran los atributos del elemento del primer nivel
let elementLevel1Attributes = elementLevel1.attributes; // Selección de los atributos del elemento del primer nivel
// Declaración de una matriz para el registro estructurado de los atributos del elemento del primer nivel
let saveAttributes = new Array();
// Iteración y registro en el objeto raíz objectToStringify de los atributos del elemento del primer nivel
for (let a = 0; a < elementLevel1Attributes.length; a++) {
// Notificación sobre el atributo del elemento del primer nivel encontrado
console.log('Atributo del elemento del primer nivel <' + elementLevel1Name + '>: ' + elementLevel1Attributes[a].name + '=' + elementLevel1Attributes[a].value);
let attributeName = elementLevel1Attributes[a].name; // Registro del nombre del atributo del elemento del primer nivel
let attributeValue = elementLevel1Attributes[a].value; // Registro del valor del atributo del elemento del primer nivel
// Registro del nombre y el valor del atributo actual del elemento del primer nivel en el objeto
let currentAttribute = {
['element' + e + 'Level1' + 'attribute' + a]: [
{
['element' + e + 'Level1' + 'attribute' + a + 'Name']: attributeName
},
{
['element' + e + 'Level1' + 'attribute' + a + 'Value']: attributeValue
}
]
};
// Guardar el atributo estructurado del elemento del primer nivel en la matriz
saveAttributes.push(currentAttribute);
};
// Registro de los atributos estructurados del elemento del primer nivel desde la matriz en el objeto raíz objectToStringify
objectToStringify['element' + e + 'Level1'][1] = {
['element' + e + 'Level1Attributes']: saveAttributes
};Si no se encontraron atributos, registramos un valor vacío.
} else {
// Si el elemento del primer nivel no tiene atributos, se muestra una notificación en la consola
console.log('El elemento del primer nivel <' + elementLevel1Name + '> no tiene atributos.');
// Si el elemento del primer nivel no tiene atributos, entonces dejamos el objeto con espacio para ellos con un valor vacío
objectToStringify['element' + e + 'Level1'][1] = {
['element' + e + 'Level1Attributes']: ''
};
};Después de los atributos, hay que registrar el contenido del elemento — es decir, el texto, los elementos anidados o el valor vacío. Para ello, hay que medir la cantidad de elementos anidados, y si no hay ninguno, comprobar la presencia de texto. Esto se hace de la siguiente manera:
// Comprobación del tipo de contenido del elemento del primer nivel
// Comprobación de la presencia de elementos del segundo nivel
if (elementLevel1.children.length > 0) {
// Si se encuentran los elementos del segundo nivel
} else if (elementLevel1.children.length == 0) {
// Si no se encuentran los elementos del segundo nivel, comprobación del elemento del primer nivel para ver si tiene texto o un valor vacío
if (elementLevel1.textContent) {
// Si hay texto
} else {
// Si no hay contenido
};
};Después de la comprobación, se registra el contenido encontrado del elemento del primer nivel:
// Registro del contenido estructurado del elemento del primer nivel desde la matriz en el objeto raíz objectToStringify
objectToStringify['element' + e + 'Level1'][2] = {
['element' + e + 'Level1Content']: elementLevel1Content
};…si el contenido son elementos del segundo nivel.
let elementLevel1Text = elementLevel1.textContent; // Selección del contenido de texto dentro del elemento del primer nivel
console.log('El elemento del primer nivel <' + elementLevel1Name + '> contiene texto: "' + elementLevel1Text + '"');
// Registro del texto del elemento del primer nivel en el objeto raíz objectToStringify
objectToStringify['element' + e + 'Level1'][2] = {
['element' + e + 'Level1Content']: elementLevel1Text
};…si el contenido es texto.
// Si el elemento del primer nivel no contiene ni elementos anidados, ni siquiera texto
console.log('El elemento del primer nivel <' + elementLevel1Name + '> no contiene texto.');
// Si el elemento del primer nivel no tiene contenido, entonces dejamos el objeto con espacio para el contenido con un valor vacío
objectToStringify['element' + e + 'Level1'][2] = {
['element' + e + 'Level1Content']: ''…si no hay contenido.
No vamos a aburrirte más con el código: los elementos del segundo y tercer nivel se registran de manera similar durante la iteración del contenido de sus elementos padre del primer nivel — esto se puede ver más abajo, en la versión final del código.
Para el segundo y tercer nivel, en sus propios bucles for, los elementos se iteran usando las variables e2 y e3, respectivamente, y en la iteración de sus atributos aparecen las variables a2 y a3 — todo de forma análoga al primer nivel.
Cuando los bucles de iteración/registro en el objeto de los elementos de todos los niveles, sus atributos y su contenido terminan, alimentamos el objeto JavaScript resultante al método incorporado JSON.stringify() para la serialización:
let result = JSON.stringify(objectToStringify); // Serializamos el objetoDespués de esto, hay que mostrar el resultado en la página usando el comando para sobreescribir el elemento <body> con el contenido de la variable result:
document.body.innerHTML = result; // Reemplazamos el cuerpo de la página con nuestro JSONPuedes ver (e incluso copiar, si tienes pereza de analizar) la versión final del código en el enlace. Como resultado de su ejecución, nuestra página se convierte en una cadena JSON y toma el siguiente aspecto:
{"element0Level1":[{"element0Level1Name":"P"},{"element0Level1Attributes":[{"element0Level1attribute0":[{"element0Level1attribute0Name":"class"},{"element0Level1attribute0Value":"myclass"}]},{"element0Level1attribute1":[{"element0Level1attribute1Name":"style"},{"element0Level1attribute1Value":"margin: 1px;"}]}]},{"element0Level1Content":"Algún párrafo."}],"element1Level1":[{"element1Level1Name":"DIV"},{"element1Level1Attributes":""},{"element1Level1Content":[{"element0Level2":[{"element0Level2Name":"P"},{"element0Level2Attributes":""},{"element0Level2Content":[{"element0Level3":[{"element0Level3Name":"SPAN"},{"element0Level3Attributes":""},{"element0Level3Content":"Otro párrafo (elemento de tercer nivel)"}]}]}]},{"element1Level2":[{"element1Level2Name":"SPAN"},{"element1Level2Attributes":""},{"element1Level2Content":"Otro párrafo (elemento de segundo nivel)"}]},{"element2Level2":[{"element2Level2Name":"SPAN"},{"element2Level2Attributes":""},{"element2Level2Content":[{"element0Level3":[{"element0Level3Name":"A"},{"element0Level3Attributes":[{"element0Level3attribute0":[{"element0Level3attribute0Name":"href"},{"element0Level3attribute0Value":"#"}]}]},{"element0Level3Content":"Algún enlace (elemento de tercer nivel)."}]}]}]},{"element3Level2":[{"element3Level2Name":"IMG"},{"element3Level2Attributes":[{"element3Level2attribute0":[{"element3Level2attribute0Name":"src"},{"element3Level2attribute0Value":"images/test.png"}]},{"element3Level2attribute1":[{"element3Level2attribute1Name":"alt"},{"element3Level2attribute1Value":"Imagen de prueba"}]}]},{"element3Level2Content":""}]}]}],"element2Level1":[{"element2Level1Name":"SCRIPT"},{"element2Level1Attributes":[{"element2Level1attribute0":[{"element2Level1attribute0Name":"src"},{"element2Level1attribute0Value":"script.js"}]}]},{"element2Level1Content":""}]}En cualquier formateador JSON, puedes ver el resultado de forma más legible.
Por si acaso, pasaremos el resultado también por un validador JSON.
Y en la consola — esta belleza.
Resultados
Hemos examinado una de las formas de convertir un archivo HTML en una cadena JSON. Puede haber muchas variantes de implementación, por lo que el aspecto del código dependerá en gran medida de tus tareas. Guiándote por el algoritmo indicado y los métodos integrados de JavaScript, podrás implementar de forma independiente un conversor similar. Y sobre cómo realizar la conversión inversa, te lo contaremos en el siguiente artículo.