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/.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 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' }, + ] + } + ] + } + ] +}; +``` diff --git a/package.json b/package.json index 7a199c7..ea77f0d 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "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", + "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", diff --git a/src/ArrayUtil.ts b/src/ArrayUtil.ts index 4e88bd1..7bf7f53 100644 --- a/src/ArrayUtil.ts +++ b/src/ArrayUtil.ts @@ -1,43 +1,26 @@ 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 */ - 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) => { - 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; + } } @@ -48,8 +31,3 @@ export interface SparqlResultLine { value: string } } - -export interface TransformedSparqlResultElement { - [propName: string]: string; -} - diff --git a/src/SparqlResultConverter.ts b/src/SparqlResultConverter.ts index 8cd6c00..10608b3 100644 --- a/src/SparqlResultConverter.ts +++ b/src/SparqlResultConverter.ts @@ -1,98 +1,103 @@ 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 { - /** - * 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; - } + + convertToDefinition(inputArray: SparqlResultLine[], mappingDefinitions: MappingDefinition[]): Record> { + const flattenedArray = ArrayUtil.extractValues(inputArray); + return this.convert(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 + */ + 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(); + + const groupedObject = groupBy(inputArray, (elem) => elem[mappingDefinition.propertyToGroup]) as Record>; + + // delete ungrouped (undefined) values + delete groupedObject["undefined"]; + + for (const key in groupedObject) { + const 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]; + }); + }); + } + + // The common element (propertyToGroup) has been extracted, delete it from sub elements + groupedElement.forEach((element) => { + delete element[mappingDefinition.propertyToGroup]; + }); + + + let objToPush = {}; + const nameToPush = { + [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 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"; + groupedArrayToPush = {[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, + }; + } else { + objToPush = { + ...nameToPush, + ...elemsToCollect, + ...groupedArrayToPush + }; + } + + outputObject[mappingDefinition.rootName].push(objToPush); + + } + + }); + return outputObject; + } } -interface MappingDefinition { - objectToGroup: string, - name: string, - childRoot: string, - toCollect?: string[]; +export interface MappingDefinition { + rootName: string, + propertyToGroup: string, + name: string, + toCollect?: string[]; + childMappings?: Partial[], } diff --git a/tests/testArrayUtil/ArrayUtil.test.ts b/tests/testArrayUtil/ArrayUtil.test.ts index 92cf6ea..d342c12 100644 --- a/tests/testArrayUtil/ArrayUtil.test.ts +++ b/tests/testArrayUtil/ArrayUtil.test.ts @@ -3,92 +3,46 @@ 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 transformedArray = ArrayUtil.extractValues(input); + assert.deepEqual(transformedArray, expectedResult, 'Expected result should have all values extracted...'); + }); - 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...'); - }); - - - 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...'); - }); }); diff --git a/tests/testConverting/expectedResults.ts b/tests/testConverting/expectedResults.ts index 53c4986..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/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..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'; @@ -9,38 +9,46 @@ 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: MappingDefinition[] = [ + { + rootName: 'owners', + propertyToGroup: 'owner', + name: 'ownerName', + childMappings: [{ + rootName: 'pets', + }] + } + ]; + + const convertedResult = resultConverter.convertToDefinition(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: MappingDefinition[] = [ + { + rootName: 'owners', + propertyToGroup: 'owner', + name: 'ownerName', + childMappings: [ + { + rootName: 'petTypes', + propertyToGroup: 'petType', + name: 'type', + childMappings: [{ + rootName: 'pets' + }] + } + ] + }, + ]; + 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 new file mode 100644 index 0000000..9052afa --- /dev/null +++ b/tests/testMultipleOnEachLayer/expectedResults.ts @@ -0,0 +1,80 @@ +// Expected result for one layer mapping: +export const expectedTwoLayerResult = { + owners: [{ + 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 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 new file mode 100644 index 0000000..ebac69c --- /dev/null +++ b/tests/testMultipleOnEachLayer/test-data.ts @@ -0,0 +1,147 @@ +/** + * Mock-up of a piece of data that could be returned from a DB-Query + */ +export const twoLayerTestData = { + head: ['owner', 'dogName', 'catName'], + results: [ + { + "owner": { + "type": "uri", + "value": "Peter" + }, + "dogName": { + "type": "uri", + "value": "Rex" + } + }, + { + "owner": { + "type": "uri", + "value": "John" + }, + "catName": { + "type": "uri", + "value": "Jimmy" + }, + "dogName": { + "type": "uri", + "value": "Lassie" + } + }, + { + "owner": { + "type": "uri", + "value": "John" + }, + "catName": { + "type": "uri", + "value": "Jimmy" + }, + "dogName": { + "type": "uri", + "value": "Oliver" + } + }, + { + "owner": { + "type": "uri", + "value": "Mary" + }, + "catName": { + "type": "uri", + "value": "Huey" + }, + "dogName": { + "type": "uri", + "value": "Louie" + } + }, + { + "owner": { + "type": "uri", + "value": "Mary" + }, + "catName": { + "type": "uri", + "value": "Dewey" + }, + "dogName": { + "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 new file mode 100644 index 0000000..dd827ee --- /dev/null +++ b/tests/testMultipleOnEachLayer/testConverting.test.ts @@ -0,0 +1,64 @@ +import { assert } from 'chai'; +import { expectedTwoLayerResult, expectedOneLayerResult } from './expectedResults'; + +import { SparqlResultConverter, MappingDefinition } from "../../src/SparqlResultConverter"; + +import {oneLayerTestData, twoLayerTestData} from './test-data'; + +const resultConverter = new SparqlResultConverter(); + + +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: MappingDefinition[] = [ + { + rootName: 'men', + propertyToGroup: 'maleOwner', + name: 'name', + childMappings: [{ + rootName: 'pets', + }], + }, + { + 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[] = [ + { + 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...'); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index aaa944e..a9f4a75 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,8 @@ "declarationMap": true, "outDir": "./dist", "composite": true, - "rootDir": "./src", - "baseUrl": "./", + "baseUrl": "./", + "rootDir": "./src", "module": "UMD", "target": "es2017", },