import { JsonapiTypeRegistry } from "../jsonapi-type-registry" ; import { JSORMBase } from "../model" ; import { IncludeDirective , IncludeScopeHash , IncludeArgHash } from "./include-directive" ; import { JsonapiResource , JsonapiResponseDoc } from "../jsonapi-spec" ; const deserialize = ( registry , datum , payload ) : JSORMBase => { const deserializer = new Deserializer ( registry , payload ) ; return deserializer . deserialize ( datum ) ; } ; const deserializeInstance = ( instance , resource , payload , includeDirective = { } ) : JSORMBase => { const deserializer = new Deserializer ( instance . klass . typeRegistry , payload ) ; return deserializer . deserializeInstance ( instance , resource , includeDirective ) ; } ; class Deserializer { payload : JsonapiResponseDoc ; registry : JsonapiTypeRegistry ; private _deserialized : JSORMBase [ ] = [ ] ; private _resources : JsonapiResource [ ] = [ ] ; constructor ( registry , payload ) { this . registry = registry ; this . payload = payload ; this . addResources ( payload . data ) ; this . addResources ( payload . included ) ; } addResources ( data ? ) { if ( ! data ) { return ; } if ( Array . isArray ( data ) ) { for ( const datum of data ) { this . _resources . push ( datum ) ; } } else { this . _resources . push ( data ) ; } } instanceFor ( type ) { const klass = this . registry . get ( type ) ; if ( ! klass ) { throw new Error ( `Unknown type "${ type }"` ) ; } return new klass ( ) ; } relationshipInstanceFor ( datum , records ) { let record = records . find ( r => { return ! ! ( r . klass . jsonapiType === datum . type && ( ( r . id && datum . id && r . id === datum . id ) || ( r . temp_id && datum [ "temp-id" ] && r . temp_id === datum [ "temp-id" ] ) ) ) ; } ) ; if ( ! record ) { record = this . instanceFor ( datum . type ) ; } return record ; } lookupAssociated ( recordSet , record ) { return recordSet . find ( r => { return ! ! ( r . klass . jsonapiType === record . klass . jsonapiType && ( ( r . temp_id && record . temp_id && r . temp_id === record . temp_id ) || ( r . id && record . id && r . id === record . id ) ) ) ; } ) ; } pushRelation ( model , associationName , record ) { const modelIdx = model as any ; const associationRecords = modelIdx [ associationName ] ; const existingInstance = this . lookupAssociated ( associationRecords , record ) ; if ( ! existingInstance ) { modelIdx [ associationName ] . push ( record ) ; } } deserialize ( datum ) { const instance = this . instanceFor ( datum . type ) ; return this . deserializeInstance ( instance , datum , { } ) ; } deserializeInstance ( instance , datum , includeDirective = { } ) { const existing = this . alreadyDeserialized ( datum ) ; if ( existing ) { return existing ; } instance . id = datum . id ; instance . temp_id = datum [ "temp-id" ] ; instance . assignAttributes ( datum . attributes ) ; instance . setMeta ( datum . meta ) ; this . _deserialized . push ( instance ) ; this . _processRelationships ( instance , datum . relationships || ( { } as any ) , includeDirective ) ; this . _removeDeletions ( instance , includeDirective ) ; instance . isPersisted = true ; instance . reset ( ) ; return instance ; } _removeDeletions ( model , includeDirective ) { Object . keys ( includeDirective ) . forEach ( key => { const modelIdx = model as any ; const relatedObjects = modelIdx [ key ] ; if ( relatedObjects ) { if ( Array . isArray ( relatedObjects ) ) { relatedObjects . forEach ( ( relatedObject , index ) => { if ( relatedObject . isMarkedForDestruction ) { modelIdx . klass . store . destroy ( relatedObject ) ; } else if ( relatedObject . isMarkedForDisassociation ) { modelIdx [ key ] . splice ( index , 1 ) ; } else { this . _removeDeletions ( relatedObject , includeDirective [ key ] || { } ) ; } } ) ; } else { const relatedObject = relatedObjects ; if ( relatedObject . isMarkedForDestruction ) { modelIdx . klass . store . destroy ( relatedObject ) ; } else if ( relatedObject . isMarkedForDisassociation ) { modelIdx [ key ] = null ; } else { this . _removeDeletions ( relatedObject , includeDirective [ key ] || { } ) ; } } } } ) ; } _processRelationships ( instance , relationships , includeDirective ) { this . _iterateValidRelationships ( instance , relationships , ( relationName , relationData ) => { const nestedIncludeDirective = includeDirective [ relationName ] ; const instanceIdx = instance as any ; if ( Array . isArray ( relationData ) ) { for ( const datum of relationData ) { const hydratedDatum = this . findResource ( datum ) ; const associationRecords = instanceIdx [ relationName ] ; let relatedInstance = this . relationshipInstanceFor ( hydratedDatum , associationRecords ) ; relatedInstance = this . deserializeInstance ( relatedInstance , hydratedDatum , nestedIncludeDirective ) ; this . pushRelation ( instance , relationName , relatedInstance ) ; } } else { const hydratedDatum = this . findResource ( relationData ) ; const existing = instanceIdx [ relationName ] ; let associated = existing || this . instanceFor ( hydratedDatum . type ) ; associated = this . deserializeInstance ( associated , hydratedDatum , nestedIncludeDirective ) ; if ( ! existing ) { instanceIdx [ relationName ] = associated ; } } } ) ; } _iterateValidRelationships ( instance , relationships , callback ) { for ( const key in relationships ) { if ( relationships . hasOwnProperty ( key ) ) { let relationName = instance . klass . deserializeKey ( key ) ; if ( instance . klass . attributeList [ relationName ] ) { const relationData = relationships [ key ] . data ; if ( ! relationData ) { continue ; } callback ( relationName , relationData ) ; } } } } alreadyDeserialized ( resourceIdentifier ) { return this . _deserialized . find ( m => { return ! ! ( m . klass . jsonapiType === resourceIdentifier . type && ( ( m . id && resourceIdentifier . id && m . id === resourceIdentifier . id ) || ( m . temp_id && resourceIdentifier . temp_id && m . temp_id === resourceIdentifier [ "temp-id" ] ) ) ) ; } ) ; } findResource ( resourceIdentifier ) { const found = this . _resources . find ( ( r ) : boolean => { return ! ! ( r . type === resourceIdentifier . type && ( ( r . id && resourceIdentifier . id && r . id === resourceIdentifier . id ) || ( r [ "temp-id" ] && resourceIdentifier [ "temp-id" ] && r [ "temp-id" ] === resourceIdentifier [ "temp-id" ] ) ) ) ; } ) ; return found || resourceIdentifier ; } } export { deserialize , deserializeInstance } ;