module io.xperiments.utils.serialize | |
{ | |
/** | |
* The mini | |
*/ | |
export interface ISerializableObject | |
{ | |
"@serializable":string; | |
} | |
export interface ISerializable extends ISerializableObject | |
{ | |
"@serializable":string; | |
writeObject( root:boolean ):any; | |
readObject( obj:any ):void; | |
} | |
export class Serializable implements ISerializable | |
{ | |
public "@serializable":string; | |
/** | |
* | |
* @returns {any} | |
*/ | |
public writeObject():any | |
{ | |
return Serializer.writeObject( this ); | |
} | |
/** | |
* | |
* @param obj | |
*/ | |
public readObject(obj:any):void | |
{ | |
Serializer.readObject(this, obj); | |
} | |
} | |
export interface ISerializableRegisters | |
{ | |
[key:string]:ISerializableRegister; | |
} | |
export interface ISerializableRegister | |
{ | |
keys:string[]; | |
serializerData:any; | |
} | |
export class Serializer | |
{ | |
private static serializableRegisters:ISerializableRegisters = {}; | |
/** | |
* | |
* @param classContext | |
* @param SerializerDataClass | |
*/ | |
public static registerClass( classContext:()=>any, SerializerDataClass:any ):void | |
{ | |
// determine class global path by parsing the body of the classContext Function | |
var classPath:string = /return ([A-Za-z0-9_$]*)/g.exec(classContext.toString())[1]; | |
// Check if class has been processed | |
if( Serializer.serializableRegisters[ classPath ] ) | |
{ | |
throw new Error('Class '+classPath+' already registered'); | |
} | |
Serializer.getClassFromPath( classPath ).prototype['@serializable'] = classPath; | |
Serializer.serializableRegisters[classPath] = | |
{ | |
keys:Serializer.getMixedNames( SerializerDataClass ), | |
serializerData:SerializerDataClass | |
}; | |
} | |
/** | |
* | |
* @param instance | |
* @returns {any} | |
*/ | |
public static writeObject( instance:ISerializable ):any | |
{ | |
var obj:any = {}; | |
var register:ISerializableRegister = Serializer.getSerializableRegister( instance ); | |
register.keys.filter((key)=>{ return key.indexOf('set_')!=0 && key.indexOf('get_')!=0 }).forEach(( key:string )=> | |
{ | |
var value:any = instance[key]; | |
if( !value ) return; // don't getSerializableProperties void/empty/undefined | |
Serializer.writeAny( value, key, obj, register.serializerData ); | |
}); | |
return obj; | |
} | |
/** | |
* | |
* @param instance | |
* @param obj | |
*/ | |
public static readObject( instance:ISerializable, obj:any ):void | |
{ | |
var register:ISerializableRegister = Serializer.getSerializableRegister( instance ); | |
Serializer.getSerializableRegister( instance ).keys | |
.forEach( ( key:string )=> Serializer.readAny( obj[key], key, instance, register.serializerData ) ); | |
} | |
// Private Methods | |
/** | |
* | |
* @param array | |
* @returns {any[]} | |
*/ | |
private static writeArray( array:any[] ):any[] | |
{ | |
var returnArray:any[] = []; | |
array.forEach( ( element , i )=> Serializer.writeAny( element,i,returnArray , Serializer.getSerializableRegister( element[i] ).serializerData) ); | |
return returnArray; | |
} | |
/** | |
* | |
* @param element | |
* @param key | |
* @param target | |
* @param SerializerDataClass | |
*/ | |
private static writeAny( element:any,key:any,target:any, SerializerDataClass:any ) | |
{ | |
if( typeof SerializerDataClass.prototype["set_"+key] == "function" ) | |
{ | |
target[key] = SerializerDataClass.prototype["set_"+key]( element ); | |
return; | |
} | |
var elementType = typeof element; | |
switch( true ) | |
{ | |
case elementType=="boolean": | |
case elementType=="string": | |
case elementType=="number": | |
target[key] = element; | |
break; | |
case Array.isArray( element ): | |
target[key] = Serializer.writeArray( element ); | |
break; | |
case elementType=="object" && !Array.isArray( element ): | |
console.log( element instanceof Date,'0000') | |
target[key] = Serializer.isExternalizable( element ) ? Serializer.writeObject( element ):JSON.parse(JSON.stringify( element )); | |
break; | |
} | |
} | |
/** | |
* | |
* @param array | |
* @returns {any[]} | |
*/ | |
private static readArray( array:any[] ):any[] | |
{ | |
var resultArray:any[] = []; | |
array.forEach( ( element, i )=>Serializer.readAny( element, i, resultArray, Serializer.getSerializableRegister( element[i] ).serializerData)); | |
return resultArray; | |
} | |
/** | |
* | |
* @param element | |
* @param key | |
* @param target | |
* @param SerializerDataClass | |
*/ | |
private static readAny( element:any, key:any, target:any, SerializerDataClass:any ) | |
{ | |
if( typeof SerializerDataClass.prototype["get_"+key] == "function" ) | |
{ | |
target[key] = SerializerDataClass.prototype["get_"+key]( element ); | |
return; | |
} | |
var type:string = typeof element; | |
switch( true ) | |
{ | |
case type=="boolean": | |
case type=="string": | |
case type=="number": | |
target[key] = element; | |
break; | |
case Array.isArray( element ): | |
target[key] = Serializer.readArray( element ); | |
break; | |
case type=="object" && !Array.isArray( element ): | |
if( element.hasOwnProperty('@serializable') ) | |
{ | |
var moduleParts:string[] = element['@serializable'].split('.'); | |
var classPath:string = moduleParts.join('.'); | |
if( !target[key] ) target[key] = Serializer.getClass(classPath); | |
target[key].readObject( element ); | |
} | |
else | |
{ | |
target[key] = element; | |
} | |
break; | |
} | |
} | |
/* Helper Methods */ | |
/** | |
* | |
* @param SerializerDataClass | |
* @returns {string[]} | |
*/ | |
private static getMixedNames( SerializerDataClass:any ):string[] | |
{ | |
return Object.getOwnPropertyNames( new SerializerDataClass() )//.concat( Object.keys( SerializerDataClass.prototype )); | |
} | |
/** | |
* | |
* @param instance | |
* @returns {boolean} | |
*/ | |
private static isExternalizable( instance ):boolean | |
{ | |
return '@serializable' in instance && typeof instance.writeObject == "function" && typeof instance.readObject == "function"; | |
} | |
/** | |
* | |
* @param name | |
* @param context | |
* @returns {any} | |
*/ | |
private static getClassFromPath( name:string , context:any = window ):any | |
{ | |
name.split('.').forEach( ctx=>context = context[ ctx ] ); | |
return context; | |
} | |
/** | |
* | |
* @param name | |
* @param context | |
* @returns {any} | |
*/ | |
private static getClass( name:string , context:any = window ):any | |
{ | |
name.split('.').forEach( ctx=>context = context[ ctx ] ); | |
return new context; | |
} | |
/** | |
* | |
* @param instance | |
* @returns {ISerializableRegister} | |
*/ | |
private static getSerializableRegister( instance:ISerializable ):ISerializableRegister | |
{ | |
var props:ISerializableRegister = Serializer.serializableRegisters[ instance['@serializable'] ]; | |
if(!props) | |
{ | |
throw new Error('Unable to get serializable properties for class '+instance['@serializable'] ) | |
} | |
return props; | |
} | |
} | |
} | |
/* Simple TEST */ | |
import ISerializableObject = io.xperiments.utils.serialize.ISerializableObject; | |
import Serializer = io.xperiments.utils.serialize.Serializer; | |
import Serializable = io.xperiments.utils.serialize.Serializable; | |
// the actual real class | |
class Data extends Serializable | |
{ | |
"@serializable":string; | |
name:string; | |
address:string; | |
date:Date; | |
constructor() | |
{ | |
super(); | |
this.name = "foo"; | |
this.address = "address"; | |
this.date = new Date(); | |
this.date.setMonth(1) | |
} | |
} | |
// here our values pattern to export | |
class DataSerializer implements ISerializableObject | |
{ | |
"@serializable":string = null; | |
name:string = null; | |
date:string = null; | |
set_date(date:Date):string{return [ date.getFullYear(), date.getMonth()+1, date.getDate()].join('/')} | |
get_date(dateString:string):Date | |
{ | |
var dateParts:string[] = dateString.split('/'); | |
var date = new Date(); | |
date.setFullYear( parseInt(dateParts[0],10)); | |
date.setMonth( parseInt(dateParts[1],10)-1); | |
date.setDate( parseInt(dateParts[2],10)); | |
return date; | |
} | |
} | |
Serializer.registerClass( ()=>Data, DataSerializer ); | |
class ISubData implements ISerializableObject | |
{ | |
"@serializable":string = null; | |
data:Data = null; | |
name:string = null; | |
date:string = null; | |
image:HTMLImageElement = null; | |
set_image( image:HTMLImageElement ):string | |
{ | |
var canvas:HTMLCanvasElement = document.createElement('canvas'); | |
canvas.width = image.width; | |
canvas.height = image.height; | |
canvas.getContext('2d').drawImage( image,0,0 ); | |
return canvas.toDataURL(); | |
} | |
get_image( image64:string ):HTMLImageElement | |
{ | |
var img:HTMLImageElement = document.createElement('img'); | |
img.src = image64; | |
return img; | |
} | |
set_date(date:Date):string{console.log('paso write');return [ date.getFullYear(), date.getMonth()+1, date.getDate()].join('/')} | |
get_date(dateString:string):Date | |
{ | |
console.log('paso read'); | |
var dateParts:string[] = dateString.split('/'); | |
var date = new Date(); | |
date.setFullYear( parseInt(dateParts[0],10)); | |
date.setMonth( parseInt(dateParts[1],10)-1); | |
date.setDate( parseInt(dateParts[2],10)); | |
return date; | |
} | |
} | |
// the actual real class | |
class SubData extends Serializable implements ISerializableObject | |
{ | |
"@serializable":string; | |
data:Data; | |
name:string = "PEDRO"; | |
date:Date = new Date(); | |
image:HTMLImageElement; | |
constructor() | |
{ | |
super(); | |
this.data = new Data(); | |
this.data.name = "SOME OTHER VALUE"; | |
this.name = "JUAN"; | |
this.data.address="SAMANIEGO 67"; | |
this.image = document.createElement('img'); | |
} | |
} | |
Serializer.registerClass( ()=>SubData, ISubData ); | |
var c = new SubData(); | |
c.name="NEW VALUE"; | |
/* ATTENTION CHANGE IMAGE HERE */ | |
c.image.src = "iphone-6-concept-Copiar.jpg"; | |
setTimeout(()=>{ | |
var d = new SubData(); | |
d.readObject( c.writeObject() ); | |
document.body.appendChild( d.image ) | |
},5000); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment