import { TEMPLATE_OBJECT_FINAL , TEMPLATE_OBJECT_START , TEMPLATE_OBJECT_END } from "../../constants" ; import { Context , InvertedIndex , CompressOptions , Compressors , TemplateCompressor } from "../common" ; import { ZipsonWriter } from "../writer" ; import { isObject } from "../util" ; type TemplateStructField = [ string ] ; type TemplateStructNestedField = [ string , TemplateStruct ] ; interface TemplateStruct extends Array < TemplateStructField | TemplateStructNestedField > { [ index ] : TemplateStructField | TemplateStructNestedField ; } export class TemplateObject implements TemplateCompressor < any > { public isTemplating = false ; private struct : TemplateStruct = [ ] ; /**
     * Create a new template object starting with two initial object that might have a shared structure
     */ constructor ( a , b ) { if ( a != null && b != null ) { this . isTemplating = buildTemplate ( a , b , this . struct ) ; } } /**
     * Compress template to writer
     */ compressTemplate ( compressors , context , invertedIndex , writer , options ) { compresObjectTemplate ( compressors , context , invertedIndex , writer , options , this . struct ) ; } /**
     * Compress object values according to structure to writer
     */ compressTemplateValues ( compressors , context , invertedIndex , writer , options , obj ) { compressObjectValues ( compressors , context , invertedIndex , writer , options , this . struct , obj ) ; } /**
     * Determine if object is templateable according to existing structure
     * If not the an ending token will be written to writer
     */ isNextTemplateable ( obj , writer ) { this . isTemplating = conformsToStructure ( this . struct , obj ) ; if ( ! this . isTemplating ) { writer . write ( TEMPLATE_OBJECT_FINAL ) ; } } /**
     * Finalize template object and write ending token
     */ end ( writer ) { writer . write ( TEMPLATE_OBJECT_FINAL ) ; } } /**
 * Build a shared template structure for two objects, returns true if they strictly share a structre
 * or false if not and a shared template structure could not be built
 */ function buildTemplate ( a , b , struct , level = 0 ) { if ( level > 6 ) { return false ; } const keysA = Object . keys ( a ) ; const keysB = Object . keys ( b ) ; if ( keysA . length !== keysB . length ) { return false ; } if ( keysA . length > 10 ) { return false ; } keysA . sort ( ( a , b ) => a . localeCompare ( b ) ) ; keysB . sort ( ( a , b ) => a . localeCompare ( b ) ) ; for ( let i = 0 ; i < keysA . length ; i ++ ) { const keyA = keysA [ i ] ; const keyB = keysB [ i ] ; if ( keyA !== keyB ) { return false ; } const valueA = a [ keyA ] ; const valueB = b [ keyB ] ; if ( isObject ( valueA ) ) { if ( ! isObject ( valueB ) ) { return false ; } const nextStruct = [ ] ; struct . push ( [ keyA , nextStruct ] ) ; if ( ! buildTemplate ( valueA , valueB , nextStruct , level + 1 ) ) { return false ; } } else if ( isObject ( valueB ) ) { return false ; } else { struct . push ( [ keyA ] ) ; } } return level > 0 || isObject ( a ) ; } /**
 * Check if an object conforms to an existing structure
 */ function conformsToStructure ( struct , obj ) { if ( ! obj ) { return false ; } if ( Object . keys ( obj ) . length !== struct . length ) { return false ; } for ( let i = 0 ; i < struct . length ; i ++ ) { const key = struct [ i ] [ 0 ] ; const isNested = struct [ i ] . length > 1 ; if ( obj [ key ] === void 0 ) { return false ; } if ( isNested ) { const x = struct [ i ] ; const y = x [ 1 ] ; if ( ! conformsToStructure ( < TemplateStruct > struct [ i ] [ 1 ] , obj [ key ] ) ) { return false ; } } else { if ( isObject ( obj [ key ] ) ) { return false ; } } } return true ; } /**
 * Compress an object template to writer
 */ function compresObjectTemplate ( compressors , context , invertedIndex , writer , options , struct ) { writer . write ( TEMPLATE_OBJECT_START ) ; for ( let i = 0 ; i < struct . length ; i ++ ) { const key = struct [ i ] [ 0 ] ; const isNested = struct [ i ] . length > 1 ; compressors . string ( compressors , context , key , invertedIndex , writer , options ) ; if ( isNested ) { compresObjectTemplate ( compressors , context , invertedIndex , writer , options , < TemplateStruct > struct [ i ] [ 1 ] ) ; } } ; writer . write ( TEMPLATE_OBJECT_END ) ; } /**
 * Compress object values according to provided structure to writer
 */ function compressObjectValues ( compressors , context , invertedIndex , writer , options , struct , obj ) { for ( let i = 0 ; i < struct . length ; i ++ ) { const key = struct [ i ] [ 0 ] ; const value = obj [ key ] ; const isNested = struct [ i ] . length > 1 ; if ( isNested ) { compressObjectValues ( compressors , context , invertedIndex , writer , options , < TemplateStruct > struct [ i ] [ 1 ] , value ) ; } else { compressors . any ( compressors , context , value , invertedIndex , writer , options ) ; } } ; }