diff --git a/packages/concerto-core/lib/serializer/jsonpopulator.js b/packages/concerto-core/lib/serializer/jsonpopulator.js index 891bf9c37..5ca8781e3 100644 --- a/packages/concerto-core/lib/serializer/jsonpopulator.js +++ b/packages/concerto-core/lib/serializer/jsonpopulator.js @@ -211,9 +211,18 @@ class JSONPopulator { * @private */ processMapType(mapDeclaration, parameters, value, type) { - let decl = mapDeclaration.getModelFile() - .getAllDeclarations() - .find(decl => decl.name === type); + let decl; + if (value && typeof value === 'object' && value.$class) { + // Use the $class property to find the class declaration + decl = mapDeclaration.getModelFile() + .getAllDeclarations() + .find(decl => decl.getFullyQualifiedName() === value.$class); + } else { + // Fallback to the original type lookup if value is not an object or doesn't have $class + decl = mapDeclaration.getModelFile() + .getAllDeclarations() + .find(decl => decl.name === type); + } // if its a ClassDeclaration, populate the Concept. if (decl?.isClassDeclaration()) { diff --git a/packages/concerto-core/test/serializer/jsonpopulator.js b/packages/concerto-core/test/serializer/jsonpopulator.js index e7ddc8a38..89e22c36c 100644 --- a/packages/concerto-core/test/serializer/jsonpopulator.js +++ b/packages/concerto-core/test/serializer/jsonpopulator.js @@ -75,6 +75,20 @@ describe('JSONPopulator', () => { o String assetId } `); + modelManager.addCTOModel(` + namespace org.acme.abstract + abstract asset Asset3 { + o String assetId + } + asset Asset4 extends Asset3 {} + map AssetByName { + o String + o Asset3 + } + concept MyContainerAsset3 { + o AssetByName assetByName + } + `); assetDeclaration1 = modelManager.getType('org.acme.MyContainerAsset1').getProperty('myAsset'); relationshipDeclaration1 = modelManager.getType('org.acme.MyTx1').getProperty('myAsset'); relationshipDeclaration2 = modelManager.getType('org.acme.MyTx2').getProperty('myAssets'); @@ -479,6 +493,29 @@ describe('JSONPopulator', () => { jsonPopulator.visit(modelManager.getType('org.acme.MyContainerAsset2'), options); }).should.throw(/Expected value at path `\$.rootObj.myAssets\[0\].assetValue` to be of type `Integer`/); }); + + it('should be able to deserialise a map that uses abstract types as values', () => { + let options = { + jsonStack: new TypedStack({ + $class: 'org.acme.abstract.MyContainerAsset3', + assetByName: { + 'asset3': { + $class: 'org.acme.abstract.Asset4' + } + } + }), + resourceStack: new TypedStack({}), + factory: mockFactory, + modelManager: modelManager + }; + + let mockResource1 = sinon.createStubInstance(Resource); + mockFactory.newResource.withArgs('org.acme.abstract', 'MyAsset4', 'asset3').returns(mockResource1); + (() => { + jsonPopulator.visit(modelManager.getType('org.acme.abstract.MyContainerAsset3'), options); + }).should.not.throw(); + }); + }); describe('#visitField', () => {