import cbor from 'cbor' ; import { TSMap } from 'typescript-map' ; import { logger } from '../../common/RadixLogger' ; import { RadixSerializableObject } from '..' ; import 'reflect-metadata' ; import { isEmpty } from '../../common/RadixUtil' ; export const JSON_PROPERTIES_KEY = 'JSON_SERIALIZATION_PROPERTIES' ; export const DSON_PROPERTIES_KEY = 'DSON_SERIALIZATION_PROPERTIES' ; param target param propertyName export function includeJSON ( target , propertyName ) { registerPropertyForSerialization ( target , propertyName , JSON_PROPERTIES_KEY ) ; } param target param propertyName export function includeDSON ( target , propertyName ) { registerPropertyForSerialization ( target , propertyName , DSON_PROPERTIES_KEY ) ; } param target param propertyName param metadataKey function registerPropertyForSerialization ( target , propertyName , metadataKey ) { if ( ! Reflect . hasOwnMetadata ( metadataKey , target ) ) { let props = [ ] ; if ( Reflect . hasMetadata ( metadataKey , target ) ) { props = Reflect . getMetadata ( metadataKey , target ) . slice ( 0 ) ; } Reflect . defineMetadata ( metadataKey , props , target ) ; } Reflect . getMetadata ( metadataKey , target ) . push ( propertyName ) ; Reflect . getMetadata ( metadataKey , target ) . sort ( ) ; } export class RadixSerializer { private static classes : TSMap < string , typeof RadixSerializableObject > = new TSMap ( ) ; private static primitives : TSMap < string , object & { fromJSON : ( input : string ) => void ; } > = new TSMap ( ) ; param serializer returns public static registerClass ( serializer ) { return ( constructor ) => { constructor . SERIALIZER = serializer ; this . classes . set ( serializer , constructor ) ; } ; } param tag returns public static registerPrimitive ( tag ) { return ( constructor ) => { this . primitives . set ( tag , constructor ) ; } ; } public static toJSON ( data ) { if ( Array . isArray ( data ) ) { const output = [ ] ; for ( const item of data ) { output . push ( RadixSerializer . toJSON ( item ) ) ; } return output ; } else if ( typeof data === 'number' || typeof data === 'boolean' ) { return data ; } else if ( typeof data === 'string' ) { return `:str:${ data }` ; } else if ( data !== null && typeof data === 'object' ) { if ( typeof data . toJSON === 'function' ) { return data . toJSON ( ) ; } else { const output = { } ; for ( const key in data ) { const serialized = RadixSerializer . toJSON ( data [ key ] ) ; if ( ! isEmpty ( serialized ) ) { output [ key ] = serialized ; } } return output ; } } } public static fromJSON ( json ) { if ( Array . isArray ( json ) ) { const output = [ ] ; for ( const item of json ) { output . push ( RadixSerializer . fromJSON ( item ) ) ; } return output ; } else if ( typeof json === 'object' ) { return RadixSerializer . fromJSONObject ( json ) ; } else if ( typeof json === 'string' ) { const tag = ( json as string ) . slice ( 0 , 5 ) ; if ( tag === ':str:' ) { return ( json as string ) . slice ( 5 ) ; } if ( this . primitives . has ( tag ) ) { return this . primitives . get ( tag ) . fromJSON ( ( json as string ) . slice ( 5 ) ) ; } logger . warn ( `No matching class for primitive string "${ json }"` ) ; } else { return json ; } } public static fromJSONObject ( jsonObject ) { const output = { } ; for ( const key in jsonObject ) { output [ key ] = ( key === 'serializer' ? jsonObject [ key ] : RadixSerializer . fromJSON ( jsonObject [ key ] ) ) ; } if ( 'serializer' in output ) { const type = output [ 'serializer' ] ; if ( this . classes . has ( type ) ) { return this . classes . get ( type ) . fromJSON ( output ) ; } } return output ; } public static toDSON ( data ) { const enc = new cbor . Encoder ( { highWaterMark : 90000 } ) ; enc . addSemanticType ( Object , ( encoder , obj ) => { const keys = Object . keys ( obj ) ; keys . sort ( ) ; if ( ! encoder . push ( Buffer . from ( [ 191 ] ) ) ) { return false ; } for ( const key of keys ) { if ( isEmpty ( obj [ key ] ) ) { continue ; } if ( ! encoder . pushAny ( key ) ) { return false ; } if ( ! encoder . pushAny ( obj [ key ] ) ) { return false ; } } if ( ! encoder . push ( Buffer . from ( [ 0xFF ] ) ) ) { return false ; } return true ; } ) ; return enc . _encodeAll ( [ data ] ) ; } }