En el desarrollo real del editor que estoy realizando me he visto en la necesidad de serializar mis objetos javascript a disco para lo que pensaba que seria suficiente con convertir mis objetos al formato JSON utilizando json2.js de https://github.com/douglascrockford/JSON-js) :
localStorage.setItem(nombredelapantalla, JSON.stringify(pantalla));
¿Cual ha sido mi sorpresa cuando he visto que este formato no admite que los objetos javascript tengan funciones? Es decir, si tenemos un objeto (función) del tipo:
function Persona(nombre, trabajo, nacimiento) {
this.nombre = nombre;
this.trabajo = trabajo;
this.nacimiento = nacimiento;
this.getNombre = function() {
return this.nombre;
}
}
Su JSON será:
{"nombre":"Pedro Picapriedra","trabajo":"Picapedrero","nacimiento":-1000}.
Aquí no existe ninguna referencia a la clase, objeto o tipo “Persona”. Si des-serializamos este objeto tendremos un nuevo objeto con los atributos nombre, trabajo, y nacimiento pero sin el método getNombre() (Sabremos que su nombre es Pedro Picapiedra, que es Picapedrero pero no sabremos si es una Persona y no le podremos preguntar su nombre).Tras varias vueltas por internet y revisar arriba y abajo JQuery sólo he visto el método toSource que convierte un objeto a un formato similar al de JSON con referencias al método y al tipo del objeto. Lástima que, una vez más, este método no esté disponible en todos los navegadores y sólo sea válido para Mozilla.
En definitiva, toca hacer en javascript algo que muchos otros lenguajes hacen de serie.
1ª Prueba: Introducimos un atributo para identificar el tipo del objeto en nuestra “clase” y lo utilizamos para regenerar el objeto copiando los valores de los atributos del objeto des-serializado por json2:
if (objetoSinMetodos.tipo == "persona") {
var nuevoPedro = new Persona();
// Copiamos los valores de los atributos
for(var member in objetoSinMetodos)
nuevoPedro[member] = objetoSinMetodos[member];
alert(nuevoPedro.getNombre() + ", " + nuevoPedro.trabajo + "," + nuevoPedro.nacimiento);
}
El ejemplo en:
2ª Prueba: Introducimos el objeto "Direccion" con sus atributos y métodos como atributo del objeto Persona. Al des-serializar queremos poder llamar a objPersona.getDireccion().getCodigoPostal().
El código siguiente se explica por sí mismo, si un atributo es un objeto le metemos la propiedad “tipo” y así sabemos de qué tipo es (aunque en el código lo creemos a capón en esta versión):
var valorAtributo;
if (objetoSinMetodos.tipo == "persona") {
var nuevoPedro = new Persona();
// Copiamos los valores de los atributos
for(var atributo in objetoSinMetodos) {
valorAtributo = objetoSinMetodos[atributo];
if (typeof valorAtributo == "object") {
var nuevaDireccion = new Direccion();
for (var atributo2 in valorAtributo)
nuevaDireccion[atributo2] = valorAtributo[atributo2];
nuevoPedro[atributo] = nuevaDireccion;
}
else
nuevoPedro[atributo] = valorAtributo;
}
alert(nuevoPedro.getDireccion().getCodigoPostal());
}
El ejemplo completo en:
3.- Quitando el “a capón”: Algo muy bueno que tiene Javascript es que a partir de una variable, un string, un nombre, podemos hacer casi todo. Podemos, en este caso, crear un nuevo objeto del tipo persona o del tipo dirección a partir de los valores, string, de los atributos tipo:
function recrearObjetoDesdeJSON(objetoDesdeJSON) {
var nuevoObjeto = new this[objetoDesdeJSON.tipo];
var valorAtributo;
for(var atributo in objetoDesdeJSON) {
valorAtributo = objetoDesdeJSON[atributo];
if (typeof valorAtributo == "object")
nuevoObjeto[atributo] = recrearObjetoDesdeJSON(valorAtributo);
else
nuevoObjeto[atributo] = valorAtributo;
}
return nuevoObjeto;
}
El ejemplo completo en:
4. Incluyendo arrays: Al ir a introducir el código anterior en mi estructura de objetos he visto que algunos de ellos tenían atributos que eran arrays por lo que la función dejaba de funcionar. Para resolver esto, debemos saber si un objeto de Javascript es un array o no. En
nos explican la solución:
function isArray(a) {
return Object.prototype.toString.apply(a) === '[object Array]';
}
Quedando, finalmente:
function recrearObjetoDesdeJSON(objetoDesdeJSON) {
if (!objetoDesdeJSON.tipo)
return objetoDesdeJSON;
var nuevoObjeto = new this[objetoDesdeJSON.tipo];
var valorAtributo;
for(var atributo in objetoDesdeJSON) {
valorAtributo = objetoDesdeJSON[atributo];
if (typeof valorAtributo == "object") {
if (isArray(valorAtributo)) {
var nuevoArray = new Array();
for (var i=0; i<valorAtributo.length; i++) {
nuevoArray[i] = recrearObjetoDesdeJSON(valorAtributo[i]);
}
nuevoObjeto[atributo] = nuevoArray;
}
else
nuevoObjeto[atributo] = recrearObjetoDesdeJSON(valorAtributo);
}
else
nuevoObjeto[atributo] = valorAtributo;
}
return nuevoObjeto;
}
Recordar que para que esto funcione cada uno de nuestros objetos deberá disponer de un atributo “tipo” con el nombre de la clase.
Es muy probable que en el caso de un atributo que sea un array de arrays el código no funcione correctamente. No es mi caso ;D.
¡CUIDADO! En mi caso tenia varios atributos que luego volvia a referencias desde otro que era un array:
this.atr1
this.atr2
this.atr3 = new Array(this.atr1, this.atr2);
Al des-serializarse el objeto con mi función los atributos del array no son los mismos objetos (son objetos del mismo tipo pero son instancias distintas). En mi caso la solución ha sido poner una función:
this.getAtributos = function() {
return new Array(this.atr1, this.atr2);
}
Estoy leyendo un buen libro de javascript "Professional javascript for web developers", tercera edición, en el que comentan el problema de codificar los objetos javascript tal y como lo estoy haciendo yo aquí, definiendo las funciones dentro del objeto con "this.funcion". Parece ser que en este caso cada vez que se crea un objeto se crea tambien uno por cada una de las funciones que aparecen en ese objeto de modo tal que si creamos dos instancias de uno de mis objetos se crearan también instancias nuevas de cada una de las funciones. En definitiva debería escribir mis "clases" de otro modo y cuando lo haga deberé buscar el modo de serializar mis objetos de nuevo. En ese momento, espero que pronto, pondré aquí la parte 2.
Por cierto, con ECMASCRIPT 6 parece ser que Javascript resolverá muchos de sus problemas. Se prodrán definir clases, constantes, etc.
Por cierto, con ECMASCRIPT 6 parece ser que Javascript resolverá muchos de sus problemas. Se prodrán definir clases, constantes, etc.
Ver Parte 2 de este tema.
No hay comentarios:
Publicar un comentario