From b981577875a0f4cbc99a9f1551840d848415d8ba Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 09:36:20 +0200 Subject: [PATCH 01/24] Set to indendation to tabs --- .editorconfig | 4 +- .eslintrc.js | 2 +- src/ArrayUtil.ts | 48 +++--- src/SparqlResultConverter.ts | 167 +++++++++---------- tests/testArrayUtil/ArrayUtil.test.ts | 160 +++++++++--------- tests/testConverting/expectedResults.ts | 160 +++++++++--------- tests/testConverting/test-data.ts | 174 ++++++++++---------- tests/testConverting/testConverting.test.ts | 63 +++---- 8 files changed, 390 insertions(+), 388 deletions(-) diff --git a/.editorconfig b/.editorconfig index ed5699b..d267a3e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,8 +8,8 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -indent_style = space -indent_size = 2 +indent_style = tab +indent_size = 4 [*.md] trim_trailing_whitespace = false diff --git a/.eslintrc.js b/.eslintrc.js index 864f46a..d7850d1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,7 +14,7 @@ module.exports = { }, rules: { "semi": ["error","always"], - "indent": ["error", 4], + "indent": ["error", "tab"], "prefer-const": ["error",{}], "max-len": ["error", {code: 140, ignoreComments: true}] // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs diff --git a/src/ArrayUtil.ts b/src/ArrayUtil.ts index 4e88bd1..e8fd0a2 100644 --- a/src/ArrayUtil.ts +++ b/src/ArrayUtil.ts @@ -1,43 +1,43 @@ export class ArrayUtil { - /** + /** * Checks whether or not all entries of the array contain the property that is used for grouping * @param {*} arrayToCheck * @param {*} groupingProperty */ - static allEntriesContainGroupingProperty(arrayToCheck: any[], groupingProperty: string): boolean { - for (let i = 0; i < arrayToCheck.length; i++) { - const element = arrayToCheck[i]; - if (!Object.prototype.hasOwnProperty.call(element, groupingProperty)) { - return false; - } - } - return true; - } + static allEntriesContainGroupingProperty(arrayToCheck: any[], groupingProperty: string): boolean { + for (let i = 0; i < arrayToCheck.length; i++) { + const element = arrayToCheck[i]; + if (!Object.prototype.hasOwnProperty.call(element, groupingProperty)) { + return false; + } + } + return true; + } - /** + /** * Transforms an array of objects to an array of simple datatypes by extracting every value-property. * @param {*} sparqlResult Array of objects that contain a value-property */ - static extractValues(sparqlResult: SparqlResultLine[]): TransformedSparqlResultElement[] { + static extractValues(sparqlResult: SparqlResultLine[]): TransformedSparqlResultElement[] { - const outputArray = new Array(); + const outputArray = new Array(); - // Take every array element and extract all values of all object keys -> flatten the array - sparqlResult.forEach((sparqlResultLine:SparqlResultLine) => { - const objectKeys = Object.keys(sparqlResultLine); + // Take every array element and extract all values of all object keys -> flatten the array + sparqlResult.forEach((sparqlResultLine:SparqlResultLine) => { + const objectKeys = Object.keys(sparqlResultLine); - const outputElement = {}; - objectKeys.forEach((key) => { - outputElement[key] = sparqlResultLine[key].value; - }); - outputArray.push(outputElement); - }); + const outputElement = {}; + objectKeys.forEach((key) => { + outputElement[key] = sparqlResultLine[key].value; + }); + outputArray.push(outputElement); + }); - return outputArray; - } + return outputArray; + } } diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 8cd6c00..202feb1 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -4,89 +4,90 @@ import { ArrayUtil, SparqlResultLine } from "./ArrayUtil"; // Maps the query result of "select_allModules" to an array of Modules export class SparqlResultConverter { - /** - * Groups a table-structure and converts it to a tree-like structure - * @param {*} inputArray An array representing data structured as a table - * @param {*} mappingDefinitions An array of objects representing the structure of the final output - */ - convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[], currElement = 0): unknown[] { - let flattenedArray; - - // first: transform array - if (currElement === 0) { - flattenedArray = ArrayUtil.extractValues(inputArray); - } else { - flattenedArray = inputArray; - } - - // get the current mapping object - const currentMappingDefition = mappingDefinitions[currElement]; - - // group the flattened array by the current mapping object's "objectToGroup" property - const groupedObject = groupBy(flattenedArray, (elem) => elem[currentMappingDefition.objectToGroup]); - - // Empty the outputArray array, it will later be filled with the grouped content - const outputArray = []; - - Object.keys(groupedObject).forEach((key) => { - let groupedElement = groupedObject[key]; - - // Collect all elements that should be collected - // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements - const elemsToCollect = {}; - if(currentMappingDefition.toCollect) { - currentMappingDefition.toCollect.forEach((elemToCollect) => { - elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; - groupedElement.forEach((inputElem) => { - delete inputElem[elemToCollect]; - }); - }); - } - - - if (currElement <= (mappingDefinitions.length - 2)) { - if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinitions[currElement + 1].objectToGroup)) { - groupedElement = (this.convert(groupedElement, mappingDefinitions, currElement + 1)); - } - } - - - // Delete the all elements that have already been grouped - groupedElement.forEach((element) => { - mappingDefinitions.forEach((mapDef) => { - delete element[mapDef.objectToGroup]; - }); - }); - - - const nameToPush = { - [currentMappingDefition.name]: key, - }; - - let objToPush = {}; - if (!isEmpty(groupedElement[0])) { - const groupToPush = { - [currentMappingDefition.childRoot]: groupedElement - }; - objToPush = { - ...nameToPush, - ...elemsToCollect, - ...groupToPush - }; - } else { - objToPush = { - ...nameToPush, - ...elemsToCollect - }; - } - - - // Add the grouped element to the outputArray - outputArray.push(objToPush); - }); - - return outputArray; - } + + /** + * Groups a table-structure and converts it to a tree-like structure + * @param {*} inputArray An array representing data structured as a table + * @param {*} mappingDefinitions An array of objects representing the structure of the final output + */ + convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[], currElement = 0): unknown[] { + let flattenedArray; + + // first: transform array + if (currElement === 0) { + flattenedArray = ArrayUtil.extractValues(inputArray); + } else { + flattenedArray = inputArray; + } + + // get the current mapping object + const currentMappingDefition = mappingDefinitions[currElement]; + + // group the flattened array by the current mapping object's "objectToGroup" property + const groupedObject = groupBy(flattenedArray, (elem) => elem[currentMappingDefition.objectToGroup]); + + // Empty the outputArray array, it will later be filled with the grouped content + const outputArray = []; + + Object.keys(groupedObject).forEach((key) => { + let groupedElement = groupedObject[key]; + + // Collect all elements that should be collected + // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements + const elemsToCollect = {}; + if(currentMappingDefition.toCollect) { + currentMappingDefition.toCollect.forEach((elemToCollect) => { + elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; + groupedElement.forEach((inputElem) => { + delete inputElem[elemToCollect]; + }); + }); + } + + + if (currElement <= (mappingDefinitions.length - 2)) { + if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinitions[currElement + 1].objectToGroup)) { + groupedElement = (this.convert(groupedElement, mappingDefinitions, currElement + 1)); + } + } + + + // Delete the all elements that have already been grouped + groupedElement.forEach((element) => { + mappingDefinitions.forEach((mapDef) => { + delete element[mapDef.objectToGroup]; + }); + }); + + + const nameToPush = { + [currentMappingDefition.name]: key, + }; + + let objToPush = {}; + if (!isEmpty(groupedElement[0])) { + const groupToPush = { + [currentMappingDefition.childRoot]: groupedElement + }; + objToPush = { + ...nameToPush, + ...elemsToCollect, + ...groupToPush + }; + } else { + objToPush = { + ...nameToPush, + ...elemsToCollect + }; + } + + + // Add the grouped element to the outputArray + outputArray.push(objToPush); + }); + + return outputArray; + } } diff --git a/tests/testArrayUtil/ArrayUtil.test.ts b/tests/testArrayUtil/ArrayUtil.test.ts index 92cf6ea..a0177c1 100644 --- a/tests/testArrayUtil/ArrayUtil.test.ts +++ b/tests/testArrayUtil/ArrayUtil.test.ts @@ -3,92 +3,92 @@ import { ArrayUtil, SparqlResultLine } from '../../src/ArrayUtil'; describe('ArrayUtil Test', () => { - it('Should extract value property of each entry', () => { - // Object that defines the structure of the result - const input: SparqlResultLine[] = - [ - { - "capability": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" - }, - "skill": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" - } - }, - { - "capability": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" - }, - "skill": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" - } - } - ]; + it('Should extract value property of each entry', () => { + // Object that defines the structure of the result + const input: SparqlResultLine[] = + [ + { + "capability": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" + }, + "skill": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" + } + }, + { + "capability": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" + }, + "skill": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" + } + } + ]; - const expectedResult = [ - { - capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", - skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" - }, - { - capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", - skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" - } - ]; + const expectedResult = [ + { + capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", + skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" + }, + { + capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", + skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" + } + ]; - const transformedArray = ArrayUtil.extractValues(input); - assert.deepEqual(transformedArray, expectedResult, 'Expected result should have all values extracted...'); - }); + const transformedArray = ArrayUtil.extractValues(input); + assert.deepEqual(transformedArray, expectedResult, 'Expected result should have all values extracted...'); + }); - it('Should confirm that all array entries contain grouping property', () => { - // Just some random array - const input = - [ - { - name: "Peter", - age: 55 - }, - { - name: "John", - age: 43 - }, - { - name: "Mary", - age: 29 - } - ]; + it('Should confirm that all array entries contain grouping property', () => { + // Just some random array + const input = + [ + { + name: "Peter", + age: 55 + }, + { + name: "John", + age: 43 + }, + { + name: "Mary", + age: 29 + } + ]; - const result = ArrayUtil.allEntriesContainGroupingProperty(input, "name"); - assert.isTrue(result, 'Should return true because all array entries contain grouping property...'); - }); + const result = ArrayUtil.allEntriesContainGroupingProperty(input, "name"); + assert.isTrue(result, 'Should return true because all array entries contain grouping property...'); + }); - it('Should confirm that not all array entries contain grouping property', () => { - // Just some random array - const input = - [ - { - name: "Peter", - age: 55, - city: "Boston" - }, - { - name: "John", - age: 43, - city: "New York City" - }, - { - name: "Mary", - age: 29 - } - ]; - const result = ArrayUtil.allEntriesContainGroupingProperty(input, "city"); - assert.isFalse(result, 'Should return false because not all array entries contain grouping property...'); - }); + it('Should confirm that not all array entries contain grouping property', () => { + // Just some random array + const input = + [ + { + name: "Peter", + age: 55, + city: "Boston" + }, + { + name: "John", + age: 43, + city: "New York City" + }, + { + name: "Mary", + age: 29 + } + ]; + const result = ArrayUtil.allEntriesContainGroupingProperty(input, "city"); + assert.isFalse(result, 'Should return false because not all array entries contain grouping property...'); + }); }); diff --git a/tests/testConverting/expectedResults.ts b/tests/testConverting/expectedResults.ts index 53c4986..3c88545 100644 --- a/tests/testConverting/expectedResults.ts +++ b/tests/testConverting/expectedResults.ts @@ -1,86 +1,86 @@ // Expected result for one layer mapping: export const expectedOneLayerResult = [ - { - ownerName: 'Peter', - pets: [ - { - petName: 'Rex', - petType: 'Dog' - } - ] - }, - { - ownerName: 'John', - pets: [ - { - petName: 'Lassie', - petType: 'Dog' - }, - { - petName: 'Oliver', - petType: 'Cat' - } - ] - }, - { - ownerName: 'Mary', - pets: [ - { - petName: 'Huey', - petType: 'Cat' - }, - { - petName: 'Dewey', - petType: 'Cat' - }, - { - petName: 'Louie', - petType: 'Cat' - } - ] - } + { + ownerName: 'Peter', + pets: [ + { + petName: 'Rex', + petType: 'Dog' + } + ] + }, + { + ownerName: 'John', + pets: [ + { + petName: 'Lassie', + petType: 'Dog' + }, + { + petName: 'Oliver', + petType: 'Cat' + } + ] + }, + { + ownerName: 'Mary', + pets: [ + { + petName: 'Huey', + petType: 'Cat' + }, + { + petName: 'Dewey', + petType: 'Cat' + }, + { + petName: 'Louie', + petType: 'Cat' + } + ] + } ]; export const expectedTwoLayerResult = [ - { - ownerName: 'Peter', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Rex' } - ] - } - ] - }, - { - ownerName: 'John', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Lassie' } - ] - }, - { - type: 'Cat', - pets: [ - { petName: 'Oliver' } - ] - } - ] - }, - { - ownerName: 'Mary', - petTypes: [ - { - type: 'Cat', - pets: [ - { petName: 'Huey' }, - { petName: 'Dewey' }, - { petName: 'Louie' }, - ] - } - ] - } + { + ownerName: 'Peter', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Rex' } + ] + } + ] + }, + { + ownerName: 'John', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Lassie' } + ] + }, + { + type: 'Cat', + pets: [ + { petName: 'Oliver' } + ] + } + ] + }, + { + ownerName: 'Mary', + petTypes: [ + { + type: 'Cat', + pets: [ + { petName: 'Huey' }, + { petName: 'Dewey' }, + { petName: 'Louie' }, + ] + } + ] + } ]; diff --git a/tests/testConverting/test-data.ts b/tests/testConverting/test-data.ts index 78f45a6..59613d8 100644 --- a/tests/testConverting/test-data.ts +++ b/tests/testConverting/test-data.ts @@ -2,91 +2,91 @@ * Mock-up of a piece of data that could be returned from a DB-Query */ export const testData = { - head: ['owner', 'petName', 'petType'], - results: [ - { - owner: { - type: 'String', - value: 'Peter', - }, - petName: { - type: 'String', - value: 'Rex' - }, - petType: { - type: 'String', - value: 'Dog' - } - }, - { - owner: { - type: 'String', - value: 'John', - }, - petName: { - type: 'String', - value: 'Lassie' - }, - petType: { - type: 'String', - value: 'Dog' - } - }, - { - owner: { - type: 'String', - value: 'John', - }, - petName: { - type: 'String', - value: 'Oliver' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - { - owner: { - type: 'String', - value: 'Mary', - }, - petName: { - type: 'String', - value: 'Huey' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - { - owner: { - type: 'String', - value: 'Mary', - }, - petName: { - type: 'String', - value: 'Dewey' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - { - owner: { - type: 'String', - value: 'Mary', - }, - petName: { - type: 'String', - value: 'Louie' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - ] + head: ['owner', 'petName', 'petType'], + results: [ + { + owner: { + type: 'String', + value: 'Peter', + }, + petName: { + type: 'String', + value: 'Rex' + }, + petType: { + type: 'String', + value: 'Dog' + } + }, + { + owner: { + type: 'String', + value: 'John', + }, + petName: { + type: 'String', + value: 'Lassie' + }, + petType: { + type: 'String', + value: 'Dog' + } + }, + { + owner: { + type: 'String', + value: 'John', + }, + petName: { + type: 'String', + value: 'Oliver' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + { + owner: { + type: 'String', + value: 'Mary', + }, + petName: { + type: 'String', + value: 'Huey' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + { + owner: { + type: 'String', + value: 'Mary', + }, + petName: { + type: 'String', + value: 'Dewey' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + { + owner: { + type: 'String', + value: 'Mary', + }, + petName: { + type: 'String', + value: 'Louie' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + ] }; diff --git a/tests/testConverting/testConverting.test.ts b/tests/testConverting/testConverting.test.ts index 0ceb7f3..f8d5ed5 100644 --- a/tests/testConverting/testConverting.test.ts +++ b/tests/testConverting/testConverting.test.ts @@ -9,38 +9,39 @@ const resultConverter = new SparqlResultConverter(); describe('One Layer Test', () => { - it('Should group a result on one layer', () => { - // Object that defines the structure of the result - const oneLayerMappingDefinition = [ - { - objectToGroup: 'owner', - name: 'ownerName', - childRoot: 'pets' - } - ]; - - const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); - assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); - }); + it('Should group a result on one layer', () => { + // Object that defines the structure of the result + const oneLayerMappingDefinition = [[ + { + objectToGroup: 'owner', + name: 'ownerName', + childRoot: 'pets' + } + ] + ]; + + const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); + }); }); describe('Two Layer Test', () => { - it('Should group a result on two layers', () => { - // Object that defines the structure of the result - const twoLayerMappingDefinition = [ - { - objectToGroup: 'owner', - name: 'ownerName', - childRoot: 'petTypes' - }, - { - objectToGroup: 'petType', - name: 'type', - childRoot: 'pets' - } - ]; - const convertedResult = resultConverter.convert(testData.results, twoLayerMappingDefinition); - - assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing two layer conversion failed...'); - }); + it('Should group a result on two layers', () => { + // Object that defines the structure of the result + const twoLayerMappingDefinition = [[ + { + objectToGroup: 'owner', + name: 'ownerName', + childRoot: 'petTypes' + }, + { + objectToGroup: 'petType', + name: 'type', + childRoot: 'pets' + } + ]]; + const convertedResult = resultConverter.convert(testData.results, twoLayerMappingDefinition); + + assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing two layer conversion failed...'); + }); }); From a00c5ff3573b7a610bda12cca210593ce127a941 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 09:36:40 +0200 Subject: [PATCH 02/24] Added tsbuildinfo to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d70577a..cd6f48e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /out-tsc # Only exists if Bazel was run /bazel-out +*.tsbuildinfo # Logs logs From d2318d5bdff0510a661a5a9b5315cc766d031e55 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 09:37:08 +0200 Subject: [PATCH 03/24] Added test for new feature, grouping of multiple properties on one layer --- .../expectedResults.ts | 87 +++++++++++++++++++ tests/testMultipleOnEachLayer/test-data.ts | 74 ++++++++++++++++ .../testConverting.test.ts | 35 ++++++++ 3 files changed, 196 insertions(+) create mode 100644 tests/testMultipleOnEachLayer/expectedResults.ts create mode 100644 tests/testMultipleOnEachLayer/test-data.ts create mode 100644 tests/testMultipleOnEachLayer/testConverting.test.ts diff --git a/tests/testMultipleOnEachLayer/expectedResults.ts b/tests/testMultipleOnEachLayer/expectedResults.ts new file mode 100644 index 0000000..a459aa7 --- /dev/null +++ b/tests/testMultipleOnEachLayer/expectedResults.ts @@ -0,0 +1,87 @@ +// Expected result for one layer mapping: +export const expectedResult = [ + { + ownerName: 'Peter', + dogs: [ + { + petName: 'Rex', + } + ] + }, + { + ownerName: 'John', + dogs: [ + { + petName: 'Lassie', + }, + { + petName: 'Oliver', + } + ], + cats: [ + { + petName: 'Jimmy' + } + ] + }, + { + ownerName: 'Mary', + cats: [ + { + petName: 'Huey', + }, + { + petName: 'Dewey', + }, + ], + dogs: [ + { + petName: 'Louie', + } + ] + } +]; + +// export const expectedTwoLayerResult = [ +// { +// ownerName: 'Peter', +// petTypes: [ +// { +// type: 'Dog', +// pets: [ +// { petName: 'Rex' } +// ] +// } +// ] +// }, +// { +// ownerName: 'John', +// petTypes: [ +// { +// type: 'Dog', +// pets: [ +// { petName: 'Lassie' } +// ] +// }, +// { +// type: 'Cat', +// pets: [ +// { petName: 'Oliver' } +// ] +// } +// ] +// }, +// { +// ownerName: 'Mary', +// petTypes: [ +// { +// type: 'Cat', +// pets: [ +// { petName: 'Huey' }, +// { petName: 'Dewey' }, +// { petName: 'Louie' }, +// ] +// } +// ] +// } +// ]; diff --git a/tests/testMultipleOnEachLayer/test-data.ts b/tests/testMultipleOnEachLayer/test-data.ts new file mode 100644 index 0000000..10b7c8f --- /dev/null +++ b/tests/testMultipleOnEachLayer/test-data.ts @@ -0,0 +1,74 @@ +/** + * Mock-up of a piece of data that could be returned from a DB-Query + */ +export const testData = { + head: ['owner', 'dogName', 'catName'], + results: [ + { + "owner": { + "type": "string", + "value": "Peter" + }, + "dogName": { + "type": "string", + "value": "Rex" + } + }, + { + "owner": { + "type": "string", + "value": "John" + }, + "catName": { + "type": "string", + "value": "Jimmy" + }, + "dogName": { + "type": "string", + "value": "Lassie" + } + }, + { + "owner": { + "type": "string", + "value": "John" + }, + "catName": { + "type": "string", + "value": "Jimmy" + }, + "dogName": { + "type": "string", + "value": "Oliver" + } + }, + { + "owner": { + "type": "string", + "value": "Mary" + }, + "catName": { + "type": "string", + "value": "Huey" + }, + "dogName": { + "type": "string", + "value": "Louie" + } + }, + { + "owner": { + "type": "string", + "value": "Mary" + }, + "catName": { + "type": "string", + "value": "Dewey" + }, + "dogName": { + "type": "string", + "value": "Louie" + } + } + ] +}; diff --git a/tests/testMultipleOnEachLayer/testConverting.test.ts b/tests/testMultipleOnEachLayer/testConverting.test.ts new file mode 100644 index 0000000..05f53d6 --- /dev/null +++ b/tests/testMultipleOnEachLayer/testConverting.test.ts @@ -0,0 +1,35 @@ +import { assert } from 'chai'; +import { expectedResult as expectedResult } from './expectedResults'; + +import { SparqlResultConverter } from "../../src/SparqlResultConverter"; + +import {testData} from './test-data'; + +const resultConverter = new SparqlResultConverter(); + + +describe('One Layer Test with multiple properties on one layer', () => { + it('Should group a result on one layer respecting multiple properties', () => { + // Object that defines the structure of the result + const oneLayerMappingDefinition = [[ + { + objectToGroup: 'owner', + name: 'ownerName', + childRoot: 'dogs' + }, + { + objectToGroup: 'dogName', + name: 'petName', + childRoot: 'cats' + }, + { + objectToGroup: 'catName', + name: 'petName', + childRoot: 'dogs' + } + ]]; + + const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedResult, 'Testing one layer conversion failed...'); + }); +}); From d1e0dde6c38fd1977534e7b53b93a0b1b64ff6b3 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 12:13:50 +0200 Subject: [PATCH 04/24] Changing interface to allow for nested mappings --- src/SparqlResultConverter.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 202feb1..3ca9a4c 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -91,9 +91,10 @@ export class SparqlResultConverter { } -interface MappingDefinition { - objectToGroup: string, - name: string, - childRoot: string, - toCollect?: string[]; +export interface MappingDefinition { + rootName: string, + objectToGroup: string, + name: string, + toCollect?: string[]; + childMappings?: MappingDefinition[], } From 244e1d9ac12da9f02fa758cbb72753cf0a94b005 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 14:38:45 +0200 Subject: [PATCH 05/24] Using TS types instead of identical custom ones --- src/ArrayUtil.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ArrayUtil.ts b/src/ArrayUtil.ts index e8fd0a2..c41bcb0 100644 --- a/src/ArrayUtil.ts +++ b/src/ArrayUtil.ts @@ -21,9 +21,9 @@ export class ArrayUtil { * Transforms an array of objects to an array of simple datatypes by extracting every value-property. * @param {*} sparqlResult Array of objects that contain a value-property */ - static extractValues(sparqlResult: SparqlResultLine[]): TransformedSparqlResultElement[] { + static extractValues(sparqlResult: SparqlResultLine[]): Record[] { - const outputArray = new Array(); + const outputArray = new Array>(); // Take every array element and extract all values of all object keys -> flatten the array sparqlResult.forEach((sparqlResultLine:SparqlResultLine) => { @@ -48,8 +48,3 @@ export interface SparqlResultLine { value: string } } - -export interface TransformedSparqlResultElement { - [propName: string]: string; -} - From 18abbad96fe86366d92389cf858a5b903539a795 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 14:39:18 +0200 Subject: [PATCH 06/24] first steps to allowing nested mappings --- src/SparqlResultConverter.ts | 148 ++++++++++++++++++++--------------- 1 file changed, 83 insertions(+), 65 deletions(-) diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 3ca9a4c..4ef034d 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -5,88 +5,106 @@ import { ArrayUtil, SparqlResultLine } from "./ArrayUtil"; // Maps the query result of "select_allModules" to an array of Modules export class SparqlResultConverter { + convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): any { + const flattenedArray = ArrayUtil.extractValues(inputArray); + return this.convert2(flattenedArray, mappingDefinitions); + } + /** * Groups a table-structure and converts it to a tree-like structure * @param {*} inputArray An array representing data structured as a table * @param {*} mappingDefinitions An array of objects representing the structure of the final output */ - convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[], currElement = 0): unknown[] { - let flattenedArray; - - // first: transform array - if (currElement === 0) { - flattenedArray = ArrayUtil.extractValues(inputArray); - } else { - flattenedArray = inputArray; - } - - // get the current mapping object - const currentMappingDefition = mappingDefinitions[currElement]; - - // group the flattened array by the current mapping object's "objectToGroup" property - const groupedObject = groupBy(flattenedArray, (elem) => elem[currentMappingDefition.objectToGroup]); - - // Empty the outputArray array, it will later be filled with the grouped content - const outputArray = []; - - Object.keys(groupedObject).forEach((key) => { - let groupedElement = groupedObject[key]; - - // Collect all elements that should be collected - // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements - const elemsToCollect = {}; - if(currentMappingDefition.toCollect) { - currentMappingDefition.toCollect.forEach((elemToCollect) => { - elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; - groupedElement.forEach((inputElem) => { - delete inputElem[elemToCollect]; + convert2(inputArray: Record[], mappingDefinitions: Partial[]): Record> { + const outputObject = {}; + + mappingDefinitions.forEach(mappingDefinition => { + outputObject[mappingDefinition.rootName] = new Array(); + + // group the flattened array by the current mapping object's "objectToGroup" property + if(!inputArray.some(elem => elem[mappingDefinition.objectToGroup])) return null; + const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.objectToGroup]) as Record; + + // delete ungrouped (undefined) values + delete groupedObject["undefined"]; + + + for (const key in groupedObject) { + let groupedElement = groupedObject[key]; + + if (groupedElement.length === 0) break; + + // Collect all elements that should be collected + // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements + const elemsToCollect = {}; + if(mappingDefinition.toCollect) { + mappingDefinition.toCollect.forEach((elemToCollect) => { + elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; + groupedElement.forEach((inputElem) => { + delete inputElem[elemToCollect]; + }); }); + } + // Delete the all elements that have already been grouped + groupedElement.forEach((element) => { + delete element[mappingDefinition.objectToGroup]; }); - } - if (currElement <= (mappingDefinitions.length - 2)) { - if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinitions[currElement + 1].objectToGroup)) { - groupedElement = (this.convert(groupedElement, mappingDefinitions, currElement + 1)); + let objToPush = {}; + const nameToPush = { + [mappingDefinition.name]: key, + }; + + + if (mappingDefinition.childMappings && mappingDefinition.childMappings[0].objectToGroup){ + // if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinition.childMappings .objectToGroup)) { + groupedElement = this.convert2(groupedElement, mappingDefinition.childMappings); + // } + + + + // if (!isEmpty(groupedElement[0])) { + + // const groupToPush = { + // [mappingDefinition.childMappings[0].rootName] : groupedElement + // }; + objToPush = { + ...nameToPush, + ...elemsToCollect, + ...groupedElement + }; + + } else { + let rootName; + (mappingDefinition.childMappings) ? rootName = mappingDefinition.childMappings[0].rootName : rootName ="children"; + + const groupToPush = { + [rootName] : groupedElement + }; + objToPush = { + ...nameToPush, + ...elemsToCollect, + ...groupToPush + }; } - } - // Delete the all elements that have already been grouped - groupedElement.forEach((element) => { - mappingDefinitions.forEach((mapDef) => { - delete element[mapDef.objectToGroup]; - }); - }); + // } else { + // objToPush = { + // ...nameToPush, + // ...elemsToCollect + // }; + // } - const nameToPush = { - [currentMappingDefition.name]: key, - }; + outputObject[mappingDefinition.rootName].push(objToPush); - let objToPush = {}; - if (!isEmpty(groupedElement[0])) { - const groupToPush = { - [currentMappingDefition.childRoot]: groupedElement - }; - objToPush = { - ...nameToPush, - ...elemsToCollect, - ...groupToPush - }; - } else { - objToPush = { - ...nameToPush, - ...elemsToCollect - }; - } + } - // Add the grouped element to the outputArray - outputArray.push(objToPush); }); - - return outputArray; + return outputObject; } } @@ -96,5 +114,5 @@ export interface MappingDefinition { objectToGroup: string, name: string, toCollect?: string[]; - childMappings?: MappingDefinition[], + childMappings?: Partial[], } From 569b53ab0ef9bccddee6deae72642cdde0264b9c Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 16:34:34 +0200 Subject: [PATCH 07/24] Fixed last problems, added comments --- src/SparqlResultConverter.ts | 71 ++++++++++++++---------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 4ef034d..084e0a8 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -1,13 +1,13 @@ import { groupBy } from "lodash"; -import { isEmpty } from "lodash"; +import isEmpty = require ("lodash"); import { ArrayUtil, SparqlResultLine } from "./ArrayUtil"; // Maps the query result of "select_allModules" to an array of Modules export class SparqlResultConverter { - convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): any { + convertToDefinition(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): Record> { const flattenedArray = ArrayUtil.extractValues(inputArray); - return this.convert2(flattenedArray, mappingDefinitions); + return this.convert(flattenedArray, mappingDefinitions); } /** @@ -15,22 +15,24 @@ export class SparqlResultConverter { * @param {*} inputArray An array representing data structured as a table * @param {*} mappingDefinitions An array of objects representing the structure of the final output */ - convert2(inputArray: Record[], mappingDefinitions: Partial[]): Record> { + private convert(inputArray: Record[], mappingDefinitions: Partial[]): Record> { const outputObject = {}; mappingDefinitions.forEach(mappingDefinition => { + + // if no element of the input array has the property to group on, there's nothing to do return + if(!inputArray.some(elem => elem[mappingDefinition.propertyToGroup])) return null; + + // create a new array with the key [rootName], this will hold the grouped array outputObject[mappingDefinition.rootName] = new Array(); - // group the flattened array by the current mapping object's "objectToGroup" property - if(!inputArray.some(elem => elem[mappingDefinition.objectToGroup])) return null; - const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.objectToGroup]) as Record; + const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.propertyToGroup]) as Record>; // delete ungrouped (undefined) values delete groupedObject["undefined"]; - for (const key in groupedObject) { - let groupedElement = groupedObject[key]; + const groupedElement = groupedObject[key]; if (groupedElement.length === 0) break; @@ -45,9 +47,10 @@ export class SparqlResultConverter { }); }); } - // Delete the all elements that have already been grouped + + // The common element (propertyToGroup) has been extracted, delete it from sub elements groupedElement.forEach((element) => { - delete element[mappingDefinition.objectToGroup]; + delete element[mappingDefinition.propertyToGroup]; }); @@ -56,51 +59,33 @@ export class SparqlResultConverter { [mappingDefinition.name]: key, }; + let groupedArrayToPush : Record>; + let rootName; + if (mappingDefinition.childMappings && mappingDefinition.childMappings[0].propertyToGroup){ + // If there are "real" childMappings (with a propertyToGroup) -> call convert() recursively on the groupedElement + groupedArrayToPush = this.convert(groupedElement, mappingDefinition.childMappings); + } else { + // If there are no more propertiesToGroup: put this element under the subElements rootName (if not set, take "children" as default) + (mappingDefinition.childMappings) ? rootName = mappingDefinition.childMappings[0].rootName : rootName ="children"; + groupedArrayToPush = {[rootName]: groupedElement}; + } - if (mappingDefinition.childMappings && mappingDefinition.childMappings[0].objectToGroup){ - // if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinition.childMappings .objectToGroup)) { - groupedElement = this.convert2(groupedElement, mappingDefinition.childMappings); - // } - - - - // if (!isEmpty(groupedElement[0])) { - - // const groupToPush = { - // [mappingDefinition.childMappings[0].rootName] : groupedElement - // }; + // if the default rootName has been used and the element is empty -> don't add groupedArrayToPush to the object to add + if(rootName == "children" && isEmpty(groupedElement[0])) { objToPush = { ...nameToPush, ...elemsToCollect, - ...groupedElement }; - } else { - let rootName; - (mappingDefinition.childMappings) ? rootName = mappingDefinition.childMappings[0].rootName : rootName ="children"; - - const groupToPush = { - [rootName] : groupedElement - }; objToPush = { ...nameToPush, ...elemsToCollect, - ...groupToPush + ...groupedArrayToPush }; } - - // } else { - // objToPush = { - // ...nameToPush, - // ...elemsToCollect - // }; - - // } - outputObject[mappingDefinition.rootName].push(objToPush); - } }); @@ -111,7 +96,7 @@ export class SparqlResultConverter { export interface MappingDefinition { rootName: string, - objectToGroup: string, + propertyToGroup: string, name: string, toCollect?: string[]; childMappings?: Partial[], From 57c854e324cd1c8ab8579e5fa6b8c03ec9de574e Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 16:34:50 +0200 Subject: [PATCH 08/24] Adapted tests and data to new structure --- tests/testConverting/expectedResults.ts | 170 +++++++++--------- tests/testConverting/testConverting.test.ts | 39 ++-- .../expectedResults.ts | 85 ++++----- tests/testMultipleOnEachLayer/test-data.ts | 103 +++++++++-- .../testConverting.test.ts | 69 ++++--- 5 files changed, 285 insertions(+), 181 deletions(-) diff --git a/tests/testConverting/expectedResults.ts b/tests/testConverting/expectedResults.ts index 3c88545..917e0d6 100644 --- a/tests/testConverting/expectedResults.ts +++ b/tests/testConverting/expectedResults.ts @@ -1,86 +1,88 @@ // Expected result for one layer mapping: -export const expectedOneLayerResult = [ - { - ownerName: 'Peter', - pets: [ - { - petName: 'Rex', - petType: 'Dog' - } - ] - }, - { - ownerName: 'John', - pets: [ - { - petName: 'Lassie', - petType: 'Dog' - }, - { - petName: 'Oliver', - petType: 'Cat' - } - ] - }, - { - ownerName: 'Mary', - pets: [ - { - petName: 'Huey', - petType: 'Cat' - }, - { - petName: 'Dewey', - petType: 'Cat' - }, - { - petName: 'Louie', - petType: 'Cat' - } - ] - } -]; +export const expectedOneLayerResult = { + owners: [ + { + ownerName: 'Peter', + pets: [ + { + petName: 'Rex', + petType: 'Dog' + } + ] + }, + { + ownerName: 'John', + pets: [ + { + petName: 'Lassie', + petType: 'Dog' + }, + { + petName: 'Oliver', + petType: 'Cat' + } + ] + }, + { + ownerName: 'Mary', + pets: [ + { + petName: 'Huey', + petType: 'Cat' + }, + { + petName: 'Dewey', + petType: 'Cat' + }, + { + petName: 'Louie', + petType: 'Cat' + } + ] + } + ]}; -export const expectedTwoLayerResult = [ - { - ownerName: 'Peter', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Rex' } - ] - } - ] - }, - { - ownerName: 'John', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Lassie' } - ] - }, - { - type: 'Cat', - pets: [ - { petName: 'Oliver' } - ] - } - ] - }, - { - ownerName: 'Mary', - petTypes: [ - { - type: 'Cat', - pets: [ - { petName: 'Huey' }, - { petName: 'Dewey' }, - { petName: 'Louie' }, - ] - } - ] - } -]; +export const expectedTwoLayerResult = { + owners: [ + { + ownerName: 'Peter', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Rex' } + ] + } + ] + }, + { + ownerName: 'John', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Lassie' } + ] + }, + { + type: 'Cat', + pets: [ + { petName: 'Oliver' } + ] + } + ] + }, + { + ownerName: 'Mary', + petTypes: [ + { + type: 'Cat', + pets: [ + { petName: 'Huey' }, + { petName: 'Dewey' }, + { petName: 'Louie' }, + ] + } + ] + } + ]}; diff --git a/tests/testConverting/testConverting.test.ts b/tests/testConverting/testConverting.test.ts index f8d5ed5..6217bff 100644 --- a/tests/testConverting/testConverting.test.ts +++ b/tests/testConverting/testConverting.test.ts @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { expectedOneLayerResult, expectedTwoLayerResult } from './expectedResults'; -import { SparqlResultConverter } from "../../src/SparqlResultConverter"; +import { SparqlResultConverter, MappingDefinition } from "../../src/SparqlResultConverter"; import {testData} from './test-data'; @@ -11,16 +11,18 @@ const resultConverter = new SparqlResultConverter(); describe('One Layer Test', () => { it('Should group a result on one layer', () => { // Object that defines the structure of the result - const oneLayerMappingDefinition = [[ + const oneLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'owner', + rootName: 'owners', + propertyToGroup: 'owner', name: 'ownerName', - childRoot: 'pets' + childMappings: [{ + rootName: 'pets', + }] } - ] ]; - const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); + const convertedResult = resultConverter.convertToDefinition(testData.results, oneLayerMappingDefinition); assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); }); }); @@ -28,19 +30,24 @@ describe('One Layer Test', () => { describe('Two Layer Test', () => { it('Should group a result on two layers', () => { // Object that defines the structure of the result - const twoLayerMappingDefinition = [[ + const twoLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'owner', + rootName: 'owners', + propertyToGroup: 'owner', name: 'ownerName', - childRoot: 'petTypes' + childMappings: [ + { + rootName: 'petTypes', + propertyToGroup: 'petType', + name: 'type', + childMappings: [{ + rootName: 'pets' + }] + } + ] }, - { - objectToGroup: 'petType', - name: 'type', - childRoot: 'pets' - } - ]]; - const convertedResult = resultConverter.convert(testData.results, twoLayerMappingDefinition); + ]; + const convertedResult = resultConverter.convertToDefinition(testData.results, twoLayerMappingDefinition); assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing two layer conversion failed...'); }); diff --git a/tests/testMultipleOnEachLayer/expectedResults.ts b/tests/testMultipleOnEachLayer/expectedResults.ts index a459aa7..9052afa 100644 --- a/tests/testMultipleOnEachLayer/expectedResults.ts +++ b/tests/testMultipleOnEachLayer/expectedResults.ts @@ -1,6 +1,6 @@ // Expected result for one layer mapping: -export const expectedResult = [ - { +export const expectedTwoLayerResult = { + owners: [{ ownerName: 'Peter', dogs: [ { @@ -40,48 +40,41 @@ export const expectedResult = [ } ] } -]; + ]}; -// export const expectedTwoLayerResult = [ -// { -// ownerName: 'Peter', -// petTypes: [ -// { -// type: 'Dog', -// pets: [ -// { petName: 'Rex' } -// ] -// } -// ] -// }, -// { -// ownerName: 'John', -// petTypes: [ -// { -// type: 'Dog', -// pets: [ -// { petName: 'Lassie' } -// ] -// }, -// { -// type: 'Cat', -// pets: [ -// { petName: 'Oliver' } -// ] -// } -// ] -// }, -// { -// ownerName: 'Mary', -// petTypes: [ -// { -// type: 'Cat', -// pets: [ -// { petName: 'Huey' }, -// { petName: 'Dewey' }, -// { petName: 'Louie' }, -// ] -// } -// ] -// } -// ]; +export const expectedOneLayerResult = { + men: [ + { + name: 'Peter', + pets: [{ + "dogName": "Rex" + }] + }, + { + name: 'John', + pets: [{ + "catName": "Jimmy", + "dogName": "Lassie" + }, + { + "catName": "Jimmy", + "dogName": "Oliver" + }] + } + ], + women: [ + { + name: 'Mary', + pets: [ + { + "catName": "Huey", + "dogName": "Louie" + }, + { + "catName": "Dewey", + "dogName": "Louie" + } + ] + } + ] +}; diff --git a/tests/testMultipleOnEachLayer/test-data.ts b/tests/testMultipleOnEachLayer/test-data.ts index 10b7c8f..ebac69c 100644 --- a/tests/testMultipleOnEachLayer/test-data.ts +++ b/tests/testMultipleOnEachLayer/test-data.ts @@ -1,72 +1,145 @@ /** * Mock-up of a piece of data that could be returned from a DB-Query */ -export const testData = { +export const twoLayerTestData = { head: ['owner', 'dogName', 'catName'], results: [ { "owner": { - "type": "string", + "type": "uri", "value": "Peter" }, "dogName": { - "type": "string", + "type": "uri", "value": "Rex" } }, { "owner": { - "type": "string", + "type": "uri", "value": "John" }, "catName": { - "type": "string", + "type": "uri", "value": "Jimmy" }, "dogName": { - "type": "string", + "type": "uri", "value": "Lassie" } }, { "owner": { - "type": "string", + "type": "uri", "value": "John" }, "catName": { - "type": "string", + "type": "uri", "value": "Jimmy" }, "dogName": { - "type": "string", + "type": "uri", "value": "Oliver" } }, { "owner": { - "type": "string", + "type": "uri", "value": "Mary" }, "catName": { - "type": "string", + "type": "uri", "value": "Huey" }, "dogName": { - "type": "string", + "type": "uri", "value": "Louie" } }, { "owner": { - "type": "string", + "type": "uri", "value": "Mary" }, "catName": { - "type": "string", + "type": "uri", "value": "Dewey" }, "dogName": { - "type": "string", + "type": "uri", + "value": "Louie" + } + } + ] +}; + + +export const oneLayerTestData = { + head: ['maleOwner', 'femaleOwner', 'dogName', 'catName'], + results: [ + { + "maleOwner": { + "type": "uri", + "value": "Peter" + }, + "dogName": { + "type": "uri", + "value": "Rex" + } + }, + { + "maleOwner": { + "type": "uri", + "value": "John" + }, + "catName": { + "type": "uri", + "value": "Jimmy" + }, + "dogName": { + "type": "uri", + "value": "Lassie" + } + }, + { + "maleOwner": { + "type": "uri", + "value": "John" + }, + "catName": { + "type": "uri", + "value": "Jimmy" + }, + "dogName": { + "type": "uri", + "value": "Oliver" + } + }, + { + "femaleOwner": { + "type": "uri", + "value": "Mary" + }, + "catName": { + "type": "uri", + "value": "Huey" + }, + "dogName": { + "type": "uri", + "value": "Louie" + } + }, + { + "femaleOwner": { + "type": "uri", + "value": "Mary" + }, + "catName": { + "type": "uri", + "value": "Dewey" + }, + "dogName": { + "type": "uri", "value": "Louie" } } diff --git a/tests/testMultipleOnEachLayer/testConverting.test.ts b/tests/testMultipleOnEachLayer/testConverting.test.ts index 05f53d6..dd827ee 100644 --- a/tests/testMultipleOnEachLayer/testConverting.test.ts +++ b/tests/testMultipleOnEachLayer/testConverting.test.ts @@ -1,35 +1,64 @@ import { assert } from 'chai'; -import { expectedResult as expectedResult } from './expectedResults'; +import { expectedTwoLayerResult, expectedOneLayerResult } from './expectedResults'; -import { SparqlResultConverter } from "../../src/SparqlResultConverter"; +import { SparqlResultConverter, MappingDefinition } from "../../src/SparqlResultConverter"; -import {testData} from './test-data'; +import {oneLayerTestData, twoLayerTestData} from './test-data'; const resultConverter = new SparqlResultConverter(); -describe('One Layer Test with multiple properties on one layer', () => { +describe('One-Layer-Test with multiple properties on this layer', () => { it('Should group a result on one layer respecting multiple properties', () => { // Object that defines the structure of the result - const oneLayerMappingDefinition = [[ + const oneLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'owner', - name: 'ownerName', - childRoot: 'dogs' + rootName: 'men', + propertyToGroup: 'maleOwner', + name: 'name', + childMappings: [{ + rootName: 'pets', + }], }, { - objectToGroup: 'dogName', - name: 'petName', - childRoot: 'cats' - }, + rootName: 'women', + propertyToGroup: 'femaleOwner', + name: 'name', + childMappings: [{ + rootName: 'pets', + }], + }]; + + const convertedResult = resultConverter.convertToDefinition(oneLayerTestData.results, oneLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); + }); +}); + + + +describe('Two-Layer-Test with multiple properties on one layer', () => { + it('Should group a result on two layers respecting multiple properties', () => { + // Object that defines the structure of the result + const twoLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'catName', - name: 'petName', - childRoot: 'dogs' - } - ]]; - - const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); - assert.deepEqual(convertedResult, expectedResult, 'Testing one layer conversion failed...'); + rootName: 'owners', + propertyToGroup: 'owner', + name: 'ownerName', + childMappings: [{ + rootName: 'dogs', + propertyToGroup: 'dogName', + name: 'petName', + }, + { + rootName: 'cats', + propertyToGroup: 'catName', + name: 'petName', + } + ] + }, + ]; + + const convertedResult = resultConverter.convertToDefinition(twoLayerTestData.results, twoLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing one layer conversion failed...'); }); }); From 494e4689d275127d9612ff2c4d2ec53740f272ed Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 16:45:29 +0200 Subject: [PATCH 09/24] Fixed tsconfig srcDir --- tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index aaa944e..b09ae63 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,6 @@ "declarationMap": true, "outDir": "./dist", "composite": true, - "rootDir": "./src", "baseUrl": "./", "module": "UMD", "target": "es2017", From e52a21615b86f53da57bf901eb1a974869cfbd5b Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 09:36:20 +0200 Subject: [PATCH 10/24] Set to indendation to tabs --- .editorconfig | 4 +- .eslintrc.js | 2 +- src/ArrayUtil.ts | 48 +++--- src/SparqlResultConverter.ts | 167 +++++++++---------- tests/testArrayUtil/ArrayUtil.test.ts | 160 +++++++++--------- tests/testConverting/expectedResults.ts | 160 +++++++++--------- tests/testConverting/test-data.ts | 174 ++++++++++---------- tests/testConverting/testConverting.test.ts | 63 +++---- 8 files changed, 390 insertions(+), 388 deletions(-) diff --git a/.editorconfig b/.editorconfig index ed5699b..d267a3e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,8 +8,8 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -indent_style = space -indent_size = 2 +indent_style = tab +indent_size = 4 [*.md] trim_trailing_whitespace = false diff --git a/.eslintrc.js b/.eslintrc.js index 864f46a..d7850d1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,7 +14,7 @@ module.exports = { }, rules: { "semi": ["error","always"], - "indent": ["error", 4], + "indent": ["error", "tab"], "prefer-const": ["error",{}], "max-len": ["error", {code: 140, ignoreComments: true}] // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs diff --git a/src/ArrayUtil.ts b/src/ArrayUtil.ts index 4e88bd1..e8fd0a2 100644 --- a/src/ArrayUtil.ts +++ b/src/ArrayUtil.ts @@ -1,43 +1,43 @@ export class ArrayUtil { - /** + /** * Checks whether or not all entries of the array contain the property that is used for grouping * @param {*} arrayToCheck * @param {*} groupingProperty */ - static allEntriesContainGroupingProperty(arrayToCheck: any[], groupingProperty: string): boolean { - for (let i = 0; i < arrayToCheck.length; i++) { - const element = arrayToCheck[i]; - if (!Object.prototype.hasOwnProperty.call(element, groupingProperty)) { - return false; - } - } - return true; - } + static allEntriesContainGroupingProperty(arrayToCheck: any[], groupingProperty: string): boolean { + for (let i = 0; i < arrayToCheck.length; i++) { + const element = arrayToCheck[i]; + if (!Object.prototype.hasOwnProperty.call(element, groupingProperty)) { + return false; + } + } + return true; + } - /** + /** * Transforms an array of objects to an array of simple datatypes by extracting every value-property. * @param {*} sparqlResult Array of objects that contain a value-property */ - static extractValues(sparqlResult: SparqlResultLine[]): TransformedSparqlResultElement[] { + static extractValues(sparqlResult: SparqlResultLine[]): TransformedSparqlResultElement[] { - const outputArray = new Array(); + const outputArray = new Array(); - // Take every array element and extract all values of all object keys -> flatten the array - sparqlResult.forEach((sparqlResultLine:SparqlResultLine) => { - const objectKeys = Object.keys(sparqlResultLine); + // Take every array element and extract all values of all object keys -> flatten the array + sparqlResult.forEach((sparqlResultLine:SparqlResultLine) => { + const objectKeys = Object.keys(sparqlResultLine); - const outputElement = {}; - objectKeys.forEach((key) => { - outputElement[key] = sparqlResultLine[key].value; - }); - outputArray.push(outputElement); - }); + const outputElement = {}; + objectKeys.forEach((key) => { + outputElement[key] = sparqlResultLine[key].value; + }); + outputArray.push(outputElement); + }); - return outputArray; - } + return outputArray; + } } diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 8cd6c00..202feb1 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -4,89 +4,90 @@ import { ArrayUtil, SparqlResultLine } from "./ArrayUtil"; // Maps the query result of "select_allModules" to an array of Modules export class SparqlResultConverter { - /** - * Groups a table-structure and converts it to a tree-like structure - * @param {*} inputArray An array representing data structured as a table - * @param {*} mappingDefinitions An array of objects representing the structure of the final output - */ - convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[], currElement = 0): unknown[] { - let flattenedArray; - - // first: transform array - if (currElement === 0) { - flattenedArray = ArrayUtil.extractValues(inputArray); - } else { - flattenedArray = inputArray; - } - - // get the current mapping object - const currentMappingDefition = mappingDefinitions[currElement]; - - // group the flattened array by the current mapping object's "objectToGroup" property - const groupedObject = groupBy(flattenedArray, (elem) => elem[currentMappingDefition.objectToGroup]); - - // Empty the outputArray array, it will later be filled with the grouped content - const outputArray = []; - - Object.keys(groupedObject).forEach((key) => { - let groupedElement = groupedObject[key]; - - // Collect all elements that should be collected - // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements - const elemsToCollect = {}; - if(currentMappingDefition.toCollect) { - currentMappingDefition.toCollect.forEach((elemToCollect) => { - elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; - groupedElement.forEach((inputElem) => { - delete inputElem[elemToCollect]; - }); - }); - } - - - if (currElement <= (mappingDefinitions.length - 2)) { - if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinitions[currElement + 1].objectToGroup)) { - groupedElement = (this.convert(groupedElement, mappingDefinitions, currElement + 1)); - } - } - - - // Delete the all elements that have already been grouped - groupedElement.forEach((element) => { - mappingDefinitions.forEach((mapDef) => { - delete element[mapDef.objectToGroup]; - }); - }); - - - const nameToPush = { - [currentMappingDefition.name]: key, - }; - - let objToPush = {}; - if (!isEmpty(groupedElement[0])) { - const groupToPush = { - [currentMappingDefition.childRoot]: groupedElement - }; - objToPush = { - ...nameToPush, - ...elemsToCollect, - ...groupToPush - }; - } else { - objToPush = { - ...nameToPush, - ...elemsToCollect - }; - } - - - // Add the grouped element to the outputArray - outputArray.push(objToPush); - }); - - return outputArray; - } + + /** + * Groups a table-structure and converts it to a tree-like structure + * @param {*} inputArray An array representing data structured as a table + * @param {*} mappingDefinitions An array of objects representing the structure of the final output + */ + convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[], currElement = 0): unknown[] { + let flattenedArray; + + // first: transform array + if (currElement === 0) { + flattenedArray = ArrayUtil.extractValues(inputArray); + } else { + flattenedArray = inputArray; + } + + // get the current mapping object + const currentMappingDefition = mappingDefinitions[currElement]; + + // group the flattened array by the current mapping object's "objectToGroup" property + const groupedObject = groupBy(flattenedArray, (elem) => elem[currentMappingDefition.objectToGroup]); + + // Empty the outputArray array, it will later be filled with the grouped content + const outputArray = []; + + Object.keys(groupedObject).forEach((key) => { + let groupedElement = groupedObject[key]; + + // Collect all elements that should be collected + // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements + const elemsToCollect = {}; + if(currentMappingDefition.toCollect) { + currentMappingDefition.toCollect.forEach((elemToCollect) => { + elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; + groupedElement.forEach((inputElem) => { + delete inputElem[elemToCollect]; + }); + }); + } + + + if (currElement <= (mappingDefinitions.length - 2)) { + if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinitions[currElement + 1].objectToGroup)) { + groupedElement = (this.convert(groupedElement, mappingDefinitions, currElement + 1)); + } + } + + + // Delete the all elements that have already been grouped + groupedElement.forEach((element) => { + mappingDefinitions.forEach((mapDef) => { + delete element[mapDef.objectToGroup]; + }); + }); + + + const nameToPush = { + [currentMappingDefition.name]: key, + }; + + let objToPush = {}; + if (!isEmpty(groupedElement[0])) { + const groupToPush = { + [currentMappingDefition.childRoot]: groupedElement + }; + objToPush = { + ...nameToPush, + ...elemsToCollect, + ...groupToPush + }; + } else { + objToPush = { + ...nameToPush, + ...elemsToCollect + }; + } + + + // Add the grouped element to the outputArray + outputArray.push(objToPush); + }); + + return outputArray; + } } diff --git a/tests/testArrayUtil/ArrayUtil.test.ts b/tests/testArrayUtil/ArrayUtil.test.ts index 92cf6ea..a0177c1 100644 --- a/tests/testArrayUtil/ArrayUtil.test.ts +++ b/tests/testArrayUtil/ArrayUtil.test.ts @@ -3,92 +3,92 @@ import { ArrayUtil, SparqlResultLine } from '../../src/ArrayUtil'; describe('ArrayUtil Test', () => { - it('Should extract value property of each entry', () => { - // Object that defines the structure of the result - const input: SparqlResultLine[] = - [ - { - "capability": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" - }, - "skill": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" - } - }, - { - "capability": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" - }, - "skill": { - "type": "uri", - "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" - } - } - ]; + it('Should extract value property of each entry', () => { + // Object that defines the structure of the result + const input: SparqlResultLine[] = + [ + { + "capability": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" + }, + "skill": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" + } + }, + { + "capability": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability" + }, + "skill": { + "type": "uri", + "value": "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" + } + } + ]; - const expectedResult = [ - { - capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", - skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" - }, - { - capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", - skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" - } - ]; + const expectedResult = [ + { + capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", + skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill" + }, + { + capability: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenCapability", + skill: "http://www.hsu-ifa.de/ontologies/capability-example#FraesenSkill_REST" + } + ]; - const transformedArray = ArrayUtil.extractValues(input); - assert.deepEqual(transformedArray, expectedResult, 'Expected result should have all values extracted...'); - }); + const transformedArray = ArrayUtil.extractValues(input); + assert.deepEqual(transformedArray, expectedResult, 'Expected result should have all values extracted...'); + }); - it('Should confirm that all array entries contain grouping property', () => { - // Just some random array - const input = - [ - { - name: "Peter", - age: 55 - }, - { - name: "John", - age: 43 - }, - { - name: "Mary", - age: 29 - } - ]; + it('Should confirm that all array entries contain grouping property', () => { + // Just some random array + const input = + [ + { + name: "Peter", + age: 55 + }, + { + name: "John", + age: 43 + }, + { + name: "Mary", + age: 29 + } + ]; - const result = ArrayUtil.allEntriesContainGroupingProperty(input, "name"); - assert.isTrue(result, 'Should return true because all array entries contain grouping property...'); - }); + const result = ArrayUtil.allEntriesContainGroupingProperty(input, "name"); + assert.isTrue(result, 'Should return true because all array entries contain grouping property...'); + }); - it('Should confirm that not all array entries contain grouping property', () => { - // Just some random array - const input = - [ - { - name: "Peter", - age: 55, - city: "Boston" - }, - { - name: "John", - age: 43, - city: "New York City" - }, - { - name: "Mary", - age: 29 - } - ]; - const result = ArrayUtil.allEntriesContainGroupingProperty(input, "city"); - assert.isFalse(result, 'Should return false because not all array entries contain grouping property...'); - }); + it('Should confirm that not all array entries contain grouping property', () => { + // Just some random array + const input = + [ + { + name: "Peter", + age: 55, + city: "Boston" + }, + { + name: "John", + age: 43, + city: "New York City" + }, + { + name: "Mary", + age: 29 + } + ]; + const result = ArrayUtil.allEntriesContainGroupingProperty(input, "city"); + assert.isFalse(result, 'Should return false because not all array entries contain grouping property...'); + }); }); diff --git a/tests/testConverting/expectedResults.ts b/tests/testConverting/expectedResults.ts index 53c4986..3c88545 100644 --- a/tests/testConverting/expectedResults.ts +++ b/tests/testConverting/expectedResults.ts @@ -1,86 +1,86 @@ // Expected result for one layer mapping: export const expectedOneLayerResult = [ - { - ownerName: 'Peter', - pets: [ - { - petName: 'Rex', - petType: 'Dog' - } - ] - }, - { - ownerName: 'John', - pets: [ - { - petName: 'Lassie', - petType: 'Dog' - }, - { - petName: 'Oliver', - petType: 'Cat' - } - ] - }, - { - ownerName: 'Mary', - pets: [ - { - petName: 'Huey', - petType: 'Cat' - }, - { - petName: 'Dewey', - petType: 'Cat' - }, - { - petName: 'Louie', - petType: 'Cat' - } - ] - } + { + ownerName: 'Peter', + pets: [ + { + petName: 'Rex', + petType: 'Dog' + } + ] + }, + { + ownerName: 'John', + pets: [ + { + petName: 'Lassie', + petType: 'Dog' + }, + { + petName: 'Oliver', + petType: 'Cat' + } + ] + }, + { + ownerName: 'Mary', + pets: [ + { + petName: 'Huey', + petType: 'Cat' + }, + { + petName: 'Dewey', + petType: 'Cat' + }, + { + petName: 'Louie', + petType: 'Cat' + } + ] + } ]; export const expectedTwoLayerResult = [ - { - ownerName: 'Peter', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Rex' } - ] - } - ] - }, - { - ownerName: 'John', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Lassie' } - ] - }, - { - type: 'Cat', - pets: [ - { petName: 'Oliver' } - ] - } - ] - }, - { - ownerName: 'Mary', - petTypes: [ - { - type: 'Cat', - pets: [ - { petName: 'Huey' }, - { petName: 'Dewey' }, - { petName: 'Louie' }, - ] - } - ] - } + { + ownerName: 'Peter', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Rex' } + ] + } + ] + }, + { + ownerName: 'John', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Lassie' } + ] + }, + { + type: 'Cat', + pets: [ + { petName: 'Oliver' } + ] + } + ] + }, + { + ownerName: 'Mary', + petTypes: [ + { + type: 'Cat', + pets: [ + { petName: 'Huey' }, + { petName: 'Dewey' }, + { petName: 'Louie' }, + ] + } + ] + } ]; diff --git a/tests/testConverting/test-data.ts b/tests/testConverting/test-data.ts index 78f45a6..59613d8 100644 --- a/tests/testConverting/test-data.ts +++ b/tests/testConverting/test-data.ts @@ -2,91 +2,91 @@ * Mock-up of a piece of data that could be returned from a DB-Query */ export const testData = { - head: ['owner', 'petName', 'petType'], - results: [ - { - owner: { - type: 'String', - value: 'Peter', - }, - petName: { - type: 'String', - value: 'Rex' - }, - petType: { - type: 'String', - value: 'Dog' - } - }, - { - owner: { - type: 'String', - value: 'John', - }, - petName: { - type: 'String', - value: 'Lassie' - }, - petType: { - type: 'String', - value: 'Dog' - } - }, - { - owner: { - type: 'String', - value: 'John', - }, - petName: { - type: 'String', - value: 'Oliver' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - { - owner: { - type: 'String', - value: 'Mary', - }, - petName: { - type: 'String', - value: 'Huey' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - { - owner: { - type: 'String', - value: 'Mary', - }, - petName: { - type: 'String', - value: 'Dewey' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - { - owner: { - type: 'String', - value: 'Mary', - }, - petName: { - type: 'String', - value: 'Louie' - }, - petType: { - type: 'String', - value: 'Cat' - } - }, - ] + head: ['owner', 'petName', 'petType'], + results: [ + { + owner: { + type: 'String', + value: 'Peter', + }, + petName: { + type: 'String', + value: 'Rex' + }, + petType: { + type: 'String', + value: 'Dog' + } + }, + { + owner: { + type: 'String', + value: 'John', + }, + petName: { + type: 'String', + value: 'Lassie' + }, + petType: { + type: 'String', + value: 'Dog' + } + }, + { + owner: { + type: 'String', + value: 'John', + }, + petName: { + type: 'String', + value: 'Oliver' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + { + owner: { + type: 'String', + value: 'Mary', + }, + petName: { + type: 'String', + value: 'Huey' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + { + owner: { + type: 'String', + value: 'Mary', + }, + petName: { + type: 'String', + value: 'Dewey' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + { + owner: { + type: 'String', + value: 'Mary', + }, + petName: { + type: 'String', + value: 'Louie' + }, + petType: { + type: 'String', + value: 'Cat' + } + }, + ] }; diff --git a/tests/testConverting/testConverting.test.ts b/tests/testConverting/testConverting.test.ts index 0ceb7f3..f8d5ed5 100644 --- a/tests/testConverting/testConverting.test.ts +++ b/tests/testConverting/testConverting.test.ts @@ -9,38 +9,39 @@ const resultConverter = new SparqlResultConverter(); describe('One Layer Test', () => { - it('Should group a result on one layer', () => { - // Object that defines the structure of the result - const oneLayerMappingDefinition = [ - { - objectToGroup: 'owner', - name: 'ownerName', - childRoot: 'pets' - } - ]; - - const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); - assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); - }); + it('Should group a result on one layer', () => { + // Object that defines the structure of the result + const oneLayerMappingDefinition = [[ + { + objectToGroup: 'owner', + name: 'ownerName', + childRoot: 'pets' + } + ] + ]; + + const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); + }); }); describe('Two Layer Test', () => { - it('Should group a result on two layers', () => { - // Object that defines the structure of the result - const twoLayerMappingDefinition = [ - { - objectToGroup: 'owner', - name: 'ownerName', - childRoot: 'petTypes' - }, - { - objectToGroup: 'petType', - name: 'type', - childRoot: 'pets' - } - ]; - const convertedResult = resultConverter.convert(testData.results, twoLayerMappingDefinition); - - assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing two layer conversion failed...'); - }); + it('Should group a result on two layers', () => { + // Object that defines the structure of the result + const twoLayerMappingDefinition = [[ + { + objectToGroup: 'owner', + name: 'ownerName', + childRoot: 'petTypes' + }, + { + objectToGroup: 'petType', + name: 'type', + childRoot: 'pets' + } + ]]; + const convertedResult = resultConverter.convert(testData.results, twoLayerMappingDefinition); + + assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing two layer conversion failed...'); + }); }); From 6d50160f05d404f79d65af8254dcd80d580e1b04 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 09:36:40 +0200 Subject: [PATCH 11/24] Added tsbuildinfo to gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index cda8e80..cd6f48e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,7 @@ /out-tsc # Only exists if Bazel was run /bazel-out -tsconfig.build.tsbuildinfo -tsconfig.tsbuildinfo +*.tsbuildinfo # Logs logs From 38c213e2d28e7ee7af2f037788aeb13d2304a6dc Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 09:37:08 +0200 Subject: [PATCH 12/24] Added test for new feature, grouping of multiple properties on one layer --- .../expectedResults.ts | 87 +++++++++++++++++++ tests/testMultipleOnEachLayer/test-data.ts | 74 ++++++++++++++++ .../testConverting.test.ts | 35 ++++++++ 3 files changed, 196 insertions(+) create mode 100644 tests/testMultipleOnEachLayer/expectedResults.ts create mode 100644 tests/testMultipleOnEachLayer/test-data.ts create mode 100644 tests/testMultipleOnEachLayer/testConverting.test.ts diff --git a/tests/testMultipleOnEachLayer/expectedResults.ts b/tests/testMultipleOnEachLayer/expectedResults.ts new file mode 100644 index 0000000..a459aa7 --- /dev/null +++ b/tests/testMultipleOnEachLayer/expectedResults.ts @@ -0,0 +1,87 @@ +// Expected result for one layer mapping: +export const expectedResult = [ + { + ownerName: 'Peter', + dogs: [ + { + petName: 'Rex', + } + ] + }, + { + ownerName: 'John', + dogs: [ + { + petName: 'Lassie', + }, + { + petName: 'Oliver', + } + ], + cats: [ + { + petName: 'Jimmy' + } + ] + }, + { + ownerName: 'Mary', + cats: [ + { + petName: 'Huey', + }, + { + petName: 'Dewey', + }, + ], + dogs: [ + { + petName: 'Louie', + } + ] + } +]; + +// export const expectedTwoLayerResult = [ +// { +// ownerName: 'Peter', +// petTypes: [ +// { +// type: 'Dog', +// pets: [ +// { petName: 'Rex' } +// ] +// } +// ] +// }, +// { +// ownerName: 'John', +// petTypes: [ +// { +// type: 'Dog', +// pets: [ +// { petName: 'Lassie' } +// ] +// }, +// { +// type: 'Cat', +// pets: [ +// { petName: 'Oliver' } +// ] +// } +// ] +// }, +// { +// ownerName: 'Mary', +// petTypes: [ +// { +// type: 'Cat', +// pets: [ +// { petName: 'Huey' }, +// { petName: 'Dewey' }, +// { petName: 'Louie' }, +// ] +// } +// ] +// } +// ]; diff --git a/tests/testMultipleOnEachLayer/test-data.ts b/tests/testMultipleOnEachLayer/test-data.ts new file mode 100644 index 0000000..10b7c8f --- /dev/null +++ b/tests/testMultipleOnEachLayer/test-data.ts @@ -0,0 +1,74 @@ +/** + * Mock-up of a piece of data that could be returned from a DB-Query + */ +export const testData = { + head: ['owner', 'dogName', 'catName'], + results: [ + { + "owner": { + "type": "string", + "value": "Peter" + }, + "dogName": { + "type": "string", + "value": "Rex" + } + }, + { + "owner": { + "type": "string", + "value": "John" + }, + "catName": { + "type": "string", + "value": "Jimmy" + }, + "dogName": { + "type": "string", + "value": "Lassie" + } + }, + { + "owner": { + "type": "string", + "value": "John" + }, + "catName": { + "type": "string", + "value": "Jimmy" + }, + "dogName": { + "type": "string", + "value": "Oliver" + } + }, + { + "owner": { + "type": "string", + "value": "Mary" + }, + "catName": { + "type": "string", + "value": "Huey" + }, + "dogName": { + "type": "string", + "value": "Louie" + } + }, + { + "owner": { + "type": "string", + "value": "Mary" + }, + "catName": { + "type": "string", + "value": "Dewey" + }, + "dogName": { + "type": "string", + "value": "Louie" + } + } + ] +}; diff --git a/tests/testMultipleOnEachLayer/testConverting.test.ts b/tests/testMultipleOnEachLayer/testConverting.test.ts new file mode 100644 index 0000000..05f53d6 --- /dev/null +++ b/tests/testMultipleOnEachLayer/testConverting.test.ts @@ -0,0 +1,35 @@ +import { assert } from 'chai'; +import { expectedResult as expectedResult } from './expectedResults'; + +import { SparqlResultConverter } from "../../src/SparqlResultConverter"; + +import {testData} from './test-data'; + +const resultConverter = new SparqlResultConverter(); + + +describe('One Layer Test with multiple properties on one layer', () => { + it('Should group a result on one layer respecting multiple properties', () => { + // Object that defines the structure of the result + const oneLayerMappingDefinition = [[ + { + objectToGroup: 'owner', + name: 'ownerName', + childRoot: 'dogs' + }, + { + objectToGroup: 'dogName', + name: 'petName', + childRoot: 'cats' + }, + { + objectToGroup: 'catName', + name: 'petName', + childRoot: 'dogs' + } + ]]; + + const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedResult, 'Testing one layer conversion failed...'); + }); +}); From bdff9df41eef7b95cac2ca1213e23be0bab0711c Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Mon, 17 Aug 2020 12:13:50 +0200 Subject: [PATCH 13/24] Changing interface to allow for nested mappings --- src/SparqlResultConverter.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 202feb1..3ca9a4c 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -91,9 +91,10 @@ export class SparqlResultConverter { } -interface MappingDefinition { - objectToGroup: string, - name: string, - childRoot: string, - toCollect?: string[]; +export interface MappingDefinition { + rootName: string, + objectToGroup: string, + name: string, + toCollect?: string[]; + childMappings?: MappingDefinition[], } From 0dc9b040a074f70148cd480dc097fe7cf3f146aa Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 14:38:45 +0200 Subject: [PATCH 14/24] Using TS types instead of identical custom ones --- src/ArrayUtil.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ArrayUtil.ts b/src/ArrayUtil.ts index e8fd0a2..c41bcb0 100644 --- a/src/ArrayUtil.ts +++ b/src/ArrayUtil.ts @@ -21,9 +21,9 @@ export class ArrayUtil { * Transforms an array of objects to an array of simple datatypes by extracting every value-property. * @param {*} sparqlResult Array of objects that contain a value-property */ - static extractValues(sparqlResult: SparqlResultLine[]): TransformedSparqlResultElement[] { + static extractValues(sparqlResult: SparqlResultLine[]): Record[] { - const outputArray = new Array(); + const outputArray = new Array>(); // Take every array element and extract all values of all object keys -> flatten the array sparqlResult.forEach((sparqlResultLine:SparqlResultLine) => { @@ -48,8 +48,3 @@ export interface SparqlResultLine { value: string } } - -export interface TransformedSparqlResultElement { - [propName: string]: string; -} - From 1969af31a283424e3b47054a8231d7ed9690be8b Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 14:39:18 +0200 Subject: [PATCH 15/24] first steps to allowing nested mappings --- src/SparqlResultConverter.ts | 148 ++++++++++++++++++++--------------- 1 file changed, 83 insertions(+), 65 deletions(-) diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 3ca9a4c..4ef034d 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -5,88 +5,106 @@ import { ArrayUtil, SparqlResultLine } from "./ArrayUtil"; // Maps the query result of "select_allModules" to an array of Modules export class SparqlResultConverter { + convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): any { + const flattenedArray = ArrayUtil.extractValues(inputArray); + return this.convert2(flattenedArray, mappingDefinitions); + } + /** * Groups a table-structure and converts it to a tree-like structure * @param {*} inputArray An array representing data structured as a table * @param {*} mappingDefinitions An array of objects representing the structure of the final output */ - convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[], currElement = 0): unknown[] { - let flattenedArray; - - // first: transform array - if (currElement === 0) { - flattenedArray = ArrayUtil.extractValues(inputArray); - } else { - flattenedArray = inputArray; - } - - // get the current mapping object - const currentMappingDefition = mappingDefinitions[currElement]; - - // group the flattened array by the current mapping object's "objectToGroup" property - const groupedObject = groupBy(flattenedArray, (elem) => elem[currentMappingDefition.objectToGroup]); - - // Empty the outputArray array, it will later be filled with the grouped content - const outputArray = []; - - Object.keys(groupedObject).forEach((key) => { - let groupedElement = groupedObject[key]; - - // Collect all elements that should be collected - // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements - const elemsToCollect = {}; - if(currentMappingDefition.toCollect) { - currentMappingDefition.toCollect.forEach((elemToCollect) => { - elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; - groupedElement.forEach((inputElem) => { - delete inputElem[elemToCollect]; + convert2(inputArray: Record[], mappingDefinitions: Partial[]): Record> { + const outputObject = {}; + + mappingDefinitions.forEach(mappingDefinition => { + outputObject[mappingDefinition.rootName] = new Array(); + + // group the flattened array by the current mapping object's "objectToGroup" property + if(!inputArray.some(elem => elem[mappingDefinition.objectToGroup])) return null; + const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.objectToGroup]) as Record; + + // delete ungrouped (undefined) values + delete groupedObject["undefined"]; + + + for (const key in groupedObject) { + let groupedElement = groupedObject[key]; + + if (groupedElement.length === 0) break; + + // Collect all elements that should be collected + // TODO: Not only take groupedElement[0], but make sure the properties to collect are equal for all groupedElements + const elemsToCollect = {}; + if(mappingDefinition.toCollect) { + mappingDefinition.toCollect.forEach((elemToCollect) => { + elemsToCollect[elemToCollect] = groupedElement[0][elemToCollect]; + groupedElement.forEach((inputElem) => { + delete inputElem[elemToCollect]; + }); }); + } + // Delete the all elements that have already been grouped + groupedElement.forEach((element) => { + delete element[mappingDefinition.objectToGroup]; }); - } - if (currElement <= (mappingDefinitions.length - 2)) { - if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinitions[currElement + 1].objectToGroup)) { - groupedElement = (this.convert(groupedElement, mappingDefinitions, currElement + 1)); + let objToPush = {}; + const nameToPush = { + [mappingDefinition.name]: key, + }; + + + if (mappingDefinition.childMappings && mappingDefinition.childMappings[0].objectToGroup){ + // if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinition.childMappings .objectToGroup)) { + groupedElement = this.convert2(groupedElement, mappingDefinition.childMappings); + // } + + + + // if (!isEmpty(groupedElement[0])) { + + // const groupToPush = { + // [mappingDefinition.childMappings[0].rootName] : groupedElement + // }; + objToPush = { + ...nameToPush, + ...elemsToCollect, + ...groupedElement + }; + + } else { + let rootName; + (mappingDefinition.childMappings) ? rootName = mappingDefinition.childMappings[0].rootName : rootName ="children"; + + const groupToPush = { + [rootName] : groupedElement + }; + objToPush = { + ...nameToPush, + ...elemsToCollect, + ...groupToPush + }; } - } - // Delete the all elements that have already been grouped - groupedElement.forEach((element) => { - mappingDefinitions.forEach((mapDef) => { - delete element[mapDef.objectToGroup]; - }); - }); + // } else { + // objToPush = { + // ...nameToPush, + // ...elemsToCollect + // }; + // } - const nameToPush = { - [currentMappingDefition.name]: key, - }; + outputObject[mappingDefinition.rootName].push(objToPush); - let objToPush = {}; - if (!isEmpty(groupedElement[0])) { - const groupToPush = { - [currentMappingDefition.childRoot]: groupedElement - }; - objToPush = { - ...nameToPush, - ...elemsToCollect, - ...groupToPush - }; - } else { - objToPush = { - ...nameToPush, - ...elemsToCollect - }; - } + } - // Add the grouped element to the outputArray - outputArray.push(objToPush); }); - - return outputArray; + return outputObject; } } @@ -96,5 +114,5 @@ export interface MappingDefinition { objectToGroup: string, name: string, toCollect?: string[]; - childMappings?: MappingDefinition[], + childMappings?: Partial[], } From 8ab8c7a4993c262c89a5dc1734245abe27feb0c0 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 16:34:34 +0200 Subject: [PATCH 16/24] Fixed last problems, added comments --- src/SparqlResultConverter.ts | 71 ++++++++++++++---------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 4ef034d..084e0a8 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -1,13 +1,13 @@ import { groupBy } from "lodash"; -import { isEmpty } from "lodash"; +import isEmpty = require ("lodash"); import { ArrayUtil, SparqlResultLine } from "./ArrayUtil"; // Maps the query result of "select_allModules" to an array of Modules export class SparqlResultConverter { - convert(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): any { + convertToDefinition(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): Record> { const flattenedArray = ArrayUtil.extractValues(inputArray); - return this.convert2(flattenedArray, mappingDefinitions); + return this.convert(flattenedArray, mappingDefinitions); } /** @@ -15,22 +15,24 @@ export class SparqlResultConverter { * @param {*} inputArray An array representing data structured as a table * @param {*} mappingDefinitions An array of objects representing the structure of the final output */ - convert2(inputArray: Record[], mappingDefinitions: Partial[]): Record> { + private convert(inputArray: Record[], mappingDefinitions: Partial[]): Record> { const outputObject = {}; mappingDefinitions.forEach(mappingDefinition => { + + // if no element of the input array has the property to group on, there's nothing to do return + if(!inputArray.some(elem => elem[mappingDefinition.propertyToGroup])) return null; + + // create a new array with the key [rootName], this will hold the grouped array outputObject[mappingDefinition.rootName] = new Array(); - // group the flattened array by the current mapping object's "objectToGroup" property - if(!inputArray.some(elem => elem[mappingDefinition.objectToGroup])) return null; - const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.objectToGroup]) as Record; + const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.propertyToGroup]) as Record>; // delete ungrouped (undefined) values delete groupedObject["undefined"]; - for (const key in groupedObject) { - let groupedElement = groupedObject[key]; + const groupedElement = groupedObject[key]; if (groupedElement.length === 0) break; @@ -45,9 +47,10 @@ export class SparqlResultConverter { }); }); } - // Delete the all elements that have already been grouped + + // The common element (propertyToGroup) has been extracted, delete it from sub elements groupedElement.forEach((element) => { - delete element[mappingDefinition.objectToGroup]; + delete element[mappingDefinition.propertyToGroup]; }); @@ -56,51 +59,33 @@ export class SparqlResultConverter { [mappingDefinition.name]: key, }; + let groupedArrayToPush : Record>; + let rootName; + if (mappingDefinition.childMappings && mappingDefinition.childMappings[0].propertyToGroup){ + // If there are "real" childMappings (with a propertyToGroup) -> call convert() recursively on the groupedElement + groupedArrayToPush = this.convert(groupedElement, mappingDefinition.childMappings); + } else { + // If there are no more propertiesToGroup: put this element under the subElements rootName (if not set, take "children" as default) + (mappingDefinition.childMappings) ? rootName = mappingDefinition.childMappings[0].rootName : rootName ="children"; + groupedArrayToPush = {[rootName]: groupedElement}; + } - if (mappingDefinition.childMappings && mappingDefinition.childMappings[0].objectToGroup){ - // if (ArrayUtil.allEntriesContainGroupingProperty(groupedElement, mappingDefinition.childMappings .objectToGroup)) { - groupedElement = this.convert2(groupedElement, mappingDefinition.childMappings); - // } - - - - // if (!isEmpty(groupedElement[0])) { - - // const groupToPush = { - // [mappingDefinition.childMappings[0].rootName] : groupedElement - // }; + // if the default rootName has been used and the element is empty -> don't add groupedArrayToPush to the object to add + if(rootName == "children" && isEmpty(groupedElement[0])) { objToPush = { ...nameToPush, ...elemsToCollect, - ...groupedElement }; - } else { - let rootName; - (mappingDefinition.childMappings) ? rootName = mappingDefinition.childMappings[0].rootName : rootName ="children"; - - const groupToPush = { - [rootName] : groupedElement - }; objToPush = { ...nameToPush, ...elemsToCollect, - ...groupToPush + ...groupedArrayToPush }; } - - // } else { - // objToPush = { - // ...nameToPush, - // ...elemsToCollect - // }; - - // } - outputObject[mappingDefinition.rootName].push(objToPush); - } }); @@ -111,7 +96,7 @@ export class SparqlResultConverter { export interface MappingDefinition { rootName: string, - objectToGroup: string, + propertyToGroup: string, name: string, toCollect?: string[]; childMappings?: Partial[], From 2966e350849f92f6cbfc41b44dae215a2daf800c Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 16:34:50 +0200 Subject: [PATCH 17/24] Adapted tests and data to new structure --- tests/testConverting/expectedResults.ts | 170 +++++++++--------- tests/testConverting/testConverting.test.ts | 39 ++-- .../expectedResults.ts | 85 ++++----- tests/testMultipleOnEachLayer/test-data.ts | 103 +++++++++-- .../testConverting.test.ts | 69 ++++--- 5 files changed, 285 insertions(+), 181 deletions(-) diff --git a/tests/testConverting/expectedResults.ts b/tests/testConverting/expectedResults.ts index 3c88545..917e0d6 100644 --- a/tests/testConverting/expectedResults.ts +++ b/tests/testConverting/expectedResults.ts @@ -1,86 +1,88 @@ // Expected result for one layer mapping: -export const expectedOneLayerResult = [ - { - ownerName: 'Peter', - pets: [ - { - petName: 'Rex', - petType: 'Dog' - } - ] - }, - { - ownerName: 'John', - pets: [ - { - petName: 'Lassie', - petType: 'Dog' - }, - { - petName: 'Oliver', - petType: 'Cat' - } - ] - }, - { - ownerName: 'Mary', - pets: [ - { - petName: 'Huey', - petType: 'Cat' - }, - { - petName: 'Dewey', - petType: 'Cat' - }, - { - petName: 'Louie', - petType: 'Cat' - } - ] - } -]; +export const expectedOneLayerResult = { + owners: [ + { + ownerName: 'Peter', + pets: [ + { + petName: 'Rex', + petType: 'Dog' + } + ] + }, + { + ownerName: 'John', + pets: [ + { + petName: 'Lassie', + petType: 'Dog' + }, + { + petName: 'Oliver', + petType: 'Cat' + } + ] + }, + { + ownerName: 'Mary', + pets: [ + { + petName: 'Huey', + petType: 'Cat' + }, + { + petName: 'Dewey', + petType: 'Cat' + }, + { + petName: 'Louie', + petType: 'Cat' + } + ] + } + ]}; -export const expectedTwoLayerResult = [ - { - ownerName: 'Peter', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Rex' } - ] - } - ] - }, - { - ownerName: 'John', - petTypes: [ - { - type: 'Dog', - pets: [ - { petName: 'Lassie' } - ] - }, - { - type: 'Cat', - pets: [ - { petName: 'Oliver' } - ] - } - ] - }, - { - ownerName: 'Mary', - petTypes: [ - { - type: 'Cat', - pets: [ - { petName: 'Huey' }, - { petName: 'Dewey' }, - { petName: 'Louie' }, - ] - } - ] - } -]; +export const expectedTwoLayerResult = { + owners: [ + { + ownerName: 'Peter', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Rex' } + ] + } + ] + }, + { + ownerName: 'John', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Lassie' } + ] + }, + { + type: 'Cat', + pets: [ + { petName: 'Oliver' } + ] + } + ] + }, + { + ownerName: 'Mary', + petTypes: [ + { + type: 'Cat', + pets: [ + { petName: 'Huey' }, + { petName: 'Dewey' }, + { petName: 'Louie' }, + ] + } + ] + } + ]}; diff --git a/tests/testConverting/testConverting.test.ts b/tests/testConverting/testConverting.test.ts index f8d5ed5..6217bff 100644 --- a/tests/testConverting/testConverting.test.ts +++ b/tests/testConverting/testConverting.test.ts @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { expectedOneLayerResult, expectedTwoLayerResult } from './expectedResults'; -import { SparqlResultConverter } from "../../src/SparqlResultConverter"; +import { SparqlResultConverter, MappingDefinition } from "../../src/SparqlResultConverter"; import {testData} from './test-data'; @@ -11,16 +11,18 @@ const resultConverter = new SparqlResultConverter(); describe('One Layer Test', () => { it('Should group a result on one layer', () => { // Object that defines the structure of the result - const oneLayerMappingDefinition = [[ + const oneLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'owner', + rootName: 'owners', + propertyToGroup: 'owner', name: 'ownerName', - childRoot: 'pets' + childMappings: [{ + rootName: 'pets', + }] } - ] ]; - const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); + const convertedResult = resultConverter.convertToDefinition(testData.results, oneLayerMappingDefinition); assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); }); }); @@ -28,19 +30,24 @@ describe('One Layer Test', () => { describe('Two Layer Test', () => { it('Should group a result on two layers', () => { // Object that defines the structure of the result - const twoLayerMappingDefinition = [[ + const twoLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'owner', + rootName: 'owners', + propertyToGroup: 'owner', name: 'ownerName', - childRoot: 'petTypes' + childMappings: [ + { + rootName: 'petTypes', + propertyToGroup: 'petType', + name: 'type', + childMappings: [{ + rootName: 'pets' + }] + } + ] }, - { - objectToGroup: 'petType', - name: 'type', - childRoot: 'pets' - } - ]]; - const convertedResult = resultConverter.convert(testData.results, twoLayerMappingDefinition); + ]; + const convertedResult = resultConverter.convertToDefinition(testData.results, twoLayerMappingDefinition); assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing two layer conversion failed...'); }); diff --git a/tests/testMultipleOnEachLayer/expectedResults.ts b/tests/testMultipleOnEachLayer/expectedResults.ts index a459aa7..9052afa 100644 --- a/tests/testMultipleOnEachLayer/expectedResults.ts +++ b/tests/testMultipleOnEachLayer/expectedResults.ts @@ -1,6 +1,6 @@ // Expected result for one layer mapping: -export const expectedResult = [ - { +export const expectedTwoLayerResult = { + owners: [{ ownerName: 'Peter', dogs: [ { @@ -40,48 +40,41 @@ export const expectedResult = [ } ] } -]; + ]}; -// export const expectedTwoLayerResult = [ -// { -// ownerName: 'Peter', -// petTypes: [ -// { -// type: 'Dog', -// pets: [ -// { petName: 'Rex' } -// ] -// } -// ] -// }, -// { -// ownerName: 'John', -// petTypes: [ -// { -// type: 'Dog', -// pets: [ -// { petName: 'Lassie' } -// ] -// }, -// { -// type: 'Cat', -// pets: [ -// { petName: 'Oliver' } -// ] -// } -// ] -// }, -// { -// ownerName: 'Mary', -// petTypes: [ -// { -// type: 'Cat', -// pets: [ -// { petName: 'Huey' }, -// { petName: 'Dewey' }, -// { petName: 'Louie' }, -// ] -// } -// ] -// } -// ]; +export const expectedOneLayerResult = { + men: [ + { + name: 'Peter', + pets: [{ + "dogName": "Rex" + }] + }, + { + name: 'John', + pets: [{ + "catName": "Jimmy", + "dogName": "Lassie" + }, + { + "catName": "Jimmy", + "dogName": "Oliver" + }] + } + ], + women: [ + { + name: 'Mary', + pets: [ + { + "catName": "Huey", + "dogName": "Louie" + }, + { + "catName": "Dewey", + "dogName": "Louie" + } + ] + } + ] +}; diff --git a/tests/testMultipleOnEachLayer/test-data.ts b/tests/testMultipleOnEachLayer/test-data.ts index 10b7c8f..ebac69c 100644 --- a/tests/testMultipleOnEachLayer/test-data.ts +++ b/tests/testMultipleOnEachLayer/test-data.ts @@ -1,72 +1,145 @@ /** * Mock-up of a piece of data that could be returned from a DB-Query */ -export const testData = { +export const twoLayerTestData = { head: ['owner', 'dogName', 'catName'], results: [ { "owner": { - "type": "string", + "type": "uri", "value": "Peter" }, "dogName": { - "type": "string", + "type": "uri", "value": "Rex" } }, { "owner": { - "type": "string", + "type": "uri", "value": "John" }, "catName": { - "type": "string", + "type": "uri", "value": "Jimmy" }, "dogName": { - "type": "string", + "type": "uri", "value": "Lassie" } }, { "owner": { - "type": "string", + "type": "uri", "value": "John" }, "catName": { - "type": "string", + "type": "uri", "value": "Jimmy" }, "dogName": { - "type": "string", + "type": "uri", "value": "Oliver" } }, { "owner": { - "type": "string", + "type": "uri", "value": "Mary" }, "catName": { - "type": "string", + "type": "uri", "value": "Huey" }, "dogName": { - "type": "string", + "type": "uri", "value": "Louie" } }, { "owner": { - "type": "string", + "type": "uri", "value": "Mary" }, "catName": { - "type": "string", + "type": "uri", "value": "Dewey" }, "dogName": { - "type": "string", + "type": "uri", + "value": "Louie" + } + } + ] +}; + + +export const oneLayerTestData = { + head: ['maleOwner', 'femaleOwner', 'dogName', 'catName'], + results: [ + { + "maleOwner": { + "type": "uri", + "value": "Peter" + }, + "dogName": { + "type": "uri", + "value": "Rex" + } + }, + { + "maleOwner": { + "type": "uri", + "value": "John" + }, + "catName": { + "type": "uri", + "value": "Jimmy" + }, + "dogName": { + "type": "uri", + "value": "Lassie" + } + }, + { + "maleOwner": { + "type": "uri", + "value": "John" + }, + "catName": { + "type": "uri", + "value": "Jimmy" + }, + "dogName": { + "type": "uri", + "value": "Oliver" + } + }, + { + "femaleOwner": { + "type": "uri", + "value": "Mary" + }, + "catName": { + "type": "uri", + "value": "Huey" + }, + "dogName": { + "type": "uri", + "value": "Louie" + } + }, + { + "femaleOwner": { + "type": "uri", + "value": "Mary" + }, + "catName": { + "type": "uri", + "value": "Dewey" + }, + "dogName": { + "type": "uri", "value": "Louie" } } diff --git a/tests/testMultipleOnEachLayer/testConverting.test.ts b/tests/testMultipleOnEachLayer/testConverting.test.ts index 05f53d6..dd827ee 100644 --- a/tests/testMultipleOnEachLayer/testConverting.test.ts +++ b/tests/testMultipleOnEachLayer/testConverting.test.ts @@ -1,35 +1,64 @@ import { assert } from 'chai'; -import { expectedResult as expectedResult } from './expectedResults'; +import { expectedTwoLayerResult, expectedOneLayerResult } from './expectedResults'; -import { SparqlResultConverter } from "../../src/SparqlResultConverter"; +import { SparqlResultConverter, MappingDefinition } from "../../src/SparqlResultConverter"; -import {testData} from './test-data'; +import {oneLayerTestData, twoLayerTestData} from './test-data'; const resultConverter = new SparqlResultConverter(); -describe('One Layer Test with multiple properties on one layer', () => { +describe('One-Layer-Test with multiple properties on this layer', () => { it('Should group a result on one layer respecting multiple properties', () => { // Object that defines the structure of the result - const oneLayerMappingDefinition = [[ + const oneLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'owner', - name: 'ownerName', - childRoot: 'dogs' + rootName: 'men', + propertyToGroup: 'maleOwner', + name: 'name', + childMappings: [{ + rootName: 'pets', + }], }, { - objectToGroup: 'dogName', - name: 'petName', - childRoot: 'cats' - }, + rootName: 'women', + propertyToGroup: 'femaleOwner', + name: 'name', + childMappings: [{ + rootName: 'pets', + }], + }]; + + const convertedResult = resultConverter.convertToDefinition(oneLayerTestData.results, oneLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedOneLayerResult, 'Testing one layer conversion failed...'); + }); +}); + + + +describe('Two-Layer-Test with multiple properties on one layer', () => { + it('Should group a result on two layers respecting multiple properties', () => { + // Object that defines the structure of the result + const twoLayerMappingDefinition: MappingDefinition[] = [ { - objectToGroup: 'catName', - name: 'petName', - childRoot: 'dogs' - } - ]]; - - const convertedResult = resultConverter.convert(testData.results, oneLayerMappingDefinition); - assert.deepEqual(convertedResult, expectedResult, 'Testing one layer conversion failed...'); + rootName: 'owners', + propertyToGroup: 'owner', + name: 'ownerName', + childMappings: [{ + rootName: 'dogs', + propertyToGroup: 'dogName', + name: 'petName', + }, + { + rootName: 'cats', + propertyToGroup: 'catName', + name: 'petName', + } + ] + }, + ]; + + const convertedResult = resultConverter.convertToDefinition(twoLayerTestData.results, twoLayerMappingDefinition); + assert.deepEqual(convertedResult, expectedTwoLayerResult, 'Testing one layer conversion failed...'); }); }); From 67d0b1892c6fa66bdbaaa0ad3089b2e4aae9d66a Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 16:45:29 +0200 Subject: [PATCH 18/24] Fixed tsconfig srcDir --- tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index aaa944e..b09ae63 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,6 @@ "declarationMap": true, "outDir": "./dist", "composite": true, - "rootDir": "./src", "baseUrl": "./", "module": "UMD", "target": "es2017", From dff662d4b7f1ebb0166a903d7804f1c6220da9c3 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 17:33:40 +0200 Subject: [PATCH 19/24] Updated documentation to match new structure --- README.md | 202 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 141 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 16ffed3..dc8d807 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,29 @@ const convertedResult = converter.convert(inputArray, mappingDefinitions); ``` You have to pass two parameters to `convert()`: 1) `inputArray` are the results you get from your SPARQL query -2) `mappingDefinitions` is an array that describes how your converted result should look like. It consists of mapping definitions looking like this: +2) `mappingDefinitions` is a recursive structure that describes how your converted result should look like. It looks like this: ```javascript - const convertStructure = [ + const convertStructure: MappingDefinition = [ { - objectToGroup: 'yourObjectToGroup', - name: 'newPropertyName', - childRoot: 'nameOfTheSubordinateArray' + rootName: 'name of the property that this group will be subordinated under' + propertyToGroup: 'a variable in your SPARQL query that you want to group on', + name: 'this can be used to rename the variable', + childMappings: [{ + // This is a Partial. + // --> You can define a complete MappingDefinition in case you want to create a nested structure + // --> You can also just set "rootName" if you don't want to group any further but want have the ungrouped "rest" under a defined rootName + }] } - ]; + ]; ``` - `objectToGroup`: The element in your query-result that you want to group (corresponds to a variable in the SELECT part of your query)
+ + `propertyToGroup`: The element in your query-result that you want to group (corresponds to a variable in the SELECT part of your query)
`name`: Can be used for mapping the result object name to a new name.
- `childRoot`: The name of the root element of the subordinate array. After grouping your `objectToGroup`, the child elements will be added as an array. This array will be given the property name `childRoot`.
+ `rootName`: The key of the grouped element in the superordinate array. After grouping, the grouped structure will be added as an array with key 'rootName' in the parent element.
## A more detailed description -When you query Triple stores with their REST API, you get a tabular structure which can be somewhat ugly if the result actually is a nested tree-like structure. The REST-API returns a direct representation of the result-table in JSON. This is not very useful when you want to use your query-results in a frontend to dynamically display your data. Let's look at an example: +When you query Triple stores with their REST API, you get a tabular structure which can be somewhat ugly if the result actually is a nested tree-like structure. The REST-API returns a direct representation of the result-table in JSON. This is not very useful when you want to use your query-results e.g. in a frontend to dynamically display your data. Let's look at an example: ![Example Graph](https://github.com/aljoshakoecher/sparql-result-converter/raw/documentation/images/docu-images/example-graph.png) @@ -161,62 +167,136 @@ The returned JSON just represents this table, the JSON looks like this: } ``` -The result is an array consisting of objects for each row of the table. Now if you want to show all owners and all pets of each owner, a nested structure is better suited. This is what sparql-result-converter gives you. It converts the flat array into a nested structure by grouping on certain properties. You can decide which properties should be grouped by passing an array representing your desired structure to the converter function.
-In this example, we want to group on the owners and would therefore pass the following array: +The result is an array consisting of objects for each row of the table. Now if you want to show all owners and all pets of each owner, a nested structure is better suited. **Converting from the tabular to a nested structure is exactly what sparql-result-converter does**. It converts the flat array into a nested structure by grouping on certain properties. You can decide which properties should be grouped by passing an array representing your desired structure to the converter function.
+In a first step of this example, we could want to group on the owners and would therefore pass the mapping definition: ```javascript -const convertStructure = [ - { - objectToGroup: 'owner', // property that should be grouped - name: 'ownerName', // new name for the grouped property - childRoot: 'pets' // name of the array that will contain the remaining properties (petName and petType) - } +const mappingDefinition: MappingDefinition[] = [ + { + rootName: 'owners', + propertyToGroup: 'owner', + name: 'name', + childMappings: [{ + rootName: 'pets', + }] + } ]; ``` -The converted array will look like this: +As a result, we want an object with key 'owners', therefore `rootName` = owners. Furthermore, we want to group on ?owner (see SPARQL query), therefore we set `propertyToGroup` to be 'owner'. As the key for the grouped array is "owners", we might want to change the owner-property to be 'name'. This is done by setting `name` to 'name'. In this first example, we don't want to group any further, but we want to make sure that the rest (i.e. the pets) is subordinated under the key 'pets', that's why we set `rootName` of the only childMapping to 'pets'. + +The converted result will look like this: ```javascript -const expectedResult = [ - { - ownerName: 'Peter', - pets: [ - { - petName: 'Rex', - petType: 'Dog' - } - ] - }, - { - ownerName: 'John', - pets: [ - { - petName: 'Lassie', - petType: 'Dog' - }, - { - petName: 'Oliver', - petType: 'Cat' - } - ] - }, - { - ownerName: 'Mary', - pets: [ - { - petName: 'Huey', - petType: 'Cat' - }, - { - petName: 'Dewey', - petType: 'Cat' - }, - { - petName: 'Louie', - petType: 'Cat' - } - ] - } -]; +{ + owners: [ + { + name: 'Peter', + pets: [ + { + petName: 'Rex', + petType: 'Dog' + } + ] + }, + { + name: 'John', + pets: [ + { + petName: 'Lassie', + petType: 'Dog' + }, + { + petName: 'Oliver', + petType: 'Cat' + } + ] + }, + { + name: 'Mary', + pets: [ + { + petName: 'Huey', + petType: 'Cat' + }, + { + petName: 'Dewey', + petType: 'Cat' + }, + { + petName: 'Louie', + petType: 'Cat' + } + ] + } + ] +}; ``` -You can use the `name` to map the `objectToGroup` to a new property name.The converter only takes the value and gets rid of the type, therefore you might want to change the property name. -You can add more objects to `convertStructure` in case you want to successively group on more properties (In this example, you could also group on 'petType'). +Now let's say we that we want to group on the owners and on the petType afterwards. This can be achieved by a nested mapping definition. Look at the following mapping definition: + +```javascript +const twoLayerMappingDefinition: MappingDefinition[] = [ + { + rootName: 'owners', + propertyToGroup: 'owner', + name: 'name', + childMappings: [ + { + rootName: 'petTypes', + propertyToGroup: 'petType', + name: 'type', + childMappings: [{ + rootName: 'pets' + }] + } + ] + }, +]; +``` +This nested mapping definition will lead to the following result: +```javascript + { + owners: [ + { + name: 'Peter', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Rex' } + ] + } + ] + }, + { + name: 'John', + petTypes: [ + { + type: 'Dog', + pets: [ + { petName: 'Lassie' } + ] + }, + { + type: 'Cat', + pets: [ + { petName: 'Oliver' } + ] + } + ] + }, + { + name: 'Mary', + petTypes: [ + { + type: 'Cat', + pets: [ + { petName: 'Huey' }, + { petName: 'Dewey' }, + { petName: 'Louie' }, + ] + } + ] + } + ] +}; +``` From b7dd938ad84df484a034cd2610be755fe44a3be0 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 17:54:54 +0200 Subject: [PATCH 20/24] Added removal of tsbuildinfo to npm clean script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a199c7..c119b8c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.3", "description": "Little utility function to that converts the table-like result of a SPARQL query into a JSON tree with a user-defined structure", "scripts": { - "clean": "rimraf dist", + "clean": "rimraf dist && rimraf *.tsbuildinfo", "build": "tsc --project tsconfig.build.json", "test": "npm run lint && npm run coverage", "test:only": "mocha -r ts-node/register tests/**/*.test.ts", From 9be2424cbe1834b268509f749096d8704016b876 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 17:55:09 +0200 Subject: [PATCH 21/24] Removed non needed function --- src/ArrayUtil.ts | 17 ---------- tests/testArrayUtil/ArrayUtil.test.ts | 46 --------------------------- 2 files changed, 63 deletions(-) diff --git a/src/ArrayUtil.ts b/src/ArrayUtil.ts index c41bcb0..7bf7f53 100644 --- a/src/ArrayUtil.ts +++ b/src/ArrayUtil.ts @@ -1,22 +1,5 @@ export class ArrayUtil { - /** - * Checks whether or not all entries of the array contain the property that is used for grouping - * @param {*} arrayToCheck - * @param {*} groupingProperty - */ - static allEntriesContainGroupingProperty(arrayToCheck: any[], groupingProperty: string): boolean { - for (let i = 0; i < arrayToCheck.length; i++) { - const element = arrayToCheck[i]; - if (!Object.prototype.hasOwnProperty.call(element, groupingProperty)) { - return false; - } - } - return true; - } - - - /** * Transforms an array of objects to an array of simple datatypes by extracting every value-property. * @param {*} sparqlResult Array of objects that contain a value-property diff --git a/tests/testArrayUtil/ArrayUtil.test.ts b/tests/testArrayUtil/ArrayUtil.test.ts index a0177c1..d342c12 100644 --- a/tests/testArrayUtil/ArrayUtil.test.ts +++ b/tests/testArrayUtil/ArrayUtil.test.ts @@ -45,50 +45,4 @@ describe('ArrayUtil Test', () => { assert.deepEqual(transformedArray, expectedResult, 'Expected result should have all values extracted...'); }); - - it('Should confirm that all array entries contain grouping property', () => { - // Just some random array - const input = - [ - { - name: "Peter", - age: 55 - }, - { - name: "John", - age: 43 - }, - { - name: "Mary", - age: 29 - } - ]; - - const result = ArrayUtil.allEntriesContainGroupingProperty(input, "name"); - assert.isTrue(result, 'Should return true because all array entries contain grouping property...'); - }); - - - it('Should confirm that not all array entries contain grouping property', () => { - // Just some random array - const input = - [ - { - name: "Peter", - age: 55, - city: "Boston" - }, - { - name: "John", - age: 43, - city: "New York City" - }, - { - name: "Mary", - age: 29 - } - ]; - const result = ArrayUtil.allEntriesContainGroupingProperty(input, "city"); - assert.isFalse(result, 'Should return false because not all array entries contain grouping property...'); - }); }); From b267058b5c6e86022bf4c5213aa8d18bee7562f4 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 17:55:37 +0200 Subject: [PATCH 22/24] Set rootDir back to ./src to fix dist output --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index b09ae63..a9f4a75 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "declarationMap": true, "outDir": "./dist", "composite": true, - "baseUrl": "./", + "baseUrl": "./", + "rootDir": "./src", "module": "UMD", "target": "es2017", }, From 67cc36d97ffdec1f6b1441c001bd6f1228dff0e4 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 17:55:58 +0200 Subject: [PATCH 23/24] changed any to unknown --- src/SparqlResultConverter.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 084e0a8..10608b3 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -5,7 +5,7 @@ import { ArrayUtil, SparqlResultLine } from "./ArrayUtil"; // Maps the query result of "select_allModules" to an array of Modules export class SparqlResultConverter { - convertToDefinition(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): Record> { + convertToDefinition(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): Record> { const flattenedArray = ArrayUtil.extractValues(inputArray); return this.convert(flattenedArray, mappingDefinitions); } @@ -15,7 +15,7 @@ export class SparqlResultConverter { * @param {*} inputArray An array representing data structured as a table * @param {*} mappingDefinitions An array of objects representing the structure of the final output */ - private convert(inputArray: Record[], mappingDefinitions: Partial[]): Record> { + private convert(inputArray: Record[], mappingDefinitions: Partial[]): Record> { const outputObject = {}; mappingDefinitions.forEach(mappingDefinition => { @@ -24,9 +24,9 @@ export class SparqlResultConverter { if(!inputArray.some(elem => elem[mappingDefinition.propertyToGroup])) return null; // create a new array with the key [rootName], this will hold the grouped array - outputObject[mappingDefinition.rootName] = new Array(); + outputObject[mappingDefinition.rootName] = new Array(); - const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.propertyToGroup]) as Record>; + const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.propertyToGroup]) as Record>; // delete ungrouped (undefined) values delete groupedObject["undefined"]; @@ -59,11 +59,11 @@ export class SparqlResultConverter { [mappingDefinition.name]: key, }; - let groupedArrayToPush : Record>; + let groupedArrayToPush : Record>; let rootName; if (mappingDefinition.childMappings && mappingDefinition.childMappings[0].propertyToGroup){ // If there are "real" childMappings (with a propertyToGroup) -> call convert() recursively on the groupedElement - groupedArrayToPush = this.convert(groupedElement, mappingDefinition.childMappings); + groupedArrayToPush = this.convert(groupedElement as Record[], mappingDefinition.childMappings); } else { // If there are no more propertiesToGroup: put this element under the subElements rootName (if not set, take "children" as default) (mappingDefinition.childMappings) ? rootName = mappingDefinition.childMappings[0].rootName : rootName ="children"; From 617f2a172ef70d06c3514ce2cca8fdab3d9b7738 Mon Sep 17 00:00:00 2001 From: Aljosha Koecher Date: Tue, 18 Aug 2020 17:57:30 +0200 Subject: [PATCH 24/24] Set version to 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c119b8c..ea77f0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sparql-result-converter", - "version": "1.0.3", + "version": "2.0.0", "description": "Little utility function to that converts the table-like result of a SPARQL query into a JSON tree with a user-defined structure", "scripts": { "clean": "rimraf dist && rimraf *.tsbuildinfo",