Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ const aCase = {
data: {
complex1: { field1: 'value1' },
field2: 'value2',
collection3: [
{id: '123', value: {field2: 'value3'}},
{id: '456', value: {field2: 'value4'}},
]
}
};

Expand All @@ -175,6 +179,10 @@ const fields = fieldExtractor(aCase);
const value1 = fields('complex1.field1');
const value2 = fields('field2');

// Extract from collections
const value3 = fields('collection3[0].value.field2'); // By index
const value4 = fields('collection3[id:456].value.field2'); // By id

// Bulk extract as array
const values = fields(['complex1.field1', 'field2', 'field3']);
// ['value1', 'value2', undefined]
Expand Down
45 changes: 38 additions & 7 deletions src/modules/case.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const RADIX_36 = 36;
const COLLECTION_ITEM_PATTERN = /^(?<name>[^\[\]]+)(?:\[(?:(?<colIndex>\d+)|id:(?<colId>[^\[\]]+))\])?$/;

/**
* @typedef CreatePayload
Expand Down Expand Up @@ -67,7 +68,12 @@ export const fieldExtractor = (aCase) => (path) => {

const singleFieldExtractor = (aCase) => (path) => {
const caseData = dataExtractor(aCase);
return caseData ? field(caseData)(path.split('.')) : undefined;
return caseData ? field(caseData)(path.split('.').map(parsePathElement)) : undefined;
};

const parsePathElement = (pathElement) => {
const match = COLLECTION_ITEM_PATTERN.exec(pathElement);
return match ? match.groups : pathElement;
};

const arrayFieldExtractor = (extractor) => (paths) => paths.map(extractor);
Expand All @@ -87,13 +93,38 @@ const dataExtractor = (aCase) => aCase && (aCase.data || aCase.case_data);

const field = (from) => (pathElements) => {
const [nextElement, ...remainingElements] = pathElements;
if (typeof from === 'object' && Object.keys(from).includes(nextElement)) {
const nextValue = from[nextElement];
if (remainingElements && remainingElements.length > 0) {
return field(nextValue)(remainingElements);
} else {
return nextValue;
const nextValue = extractNextElement(from, nextElement);

if (remainingElements && remainingElements.length > 0) {
return field(nextValue)(remainingElements);
} else {
return nextValue;
}
};

const isObjectWithKey = (obj, key) => obj && typeof obj === 'object' && Object.keys(obj).includes(key);

const extractCollectionItem = (collection, {colIndex, colId}) => {
if (!Array.isArray(collection)) {
return undefined;
}

if (colId) {
return collection.find((item) => item.id === colId);
}

return collection[parseInt(colIndex)];
};

const extractNextElement = (from, {name, colIndex, colId}) => {
if (isObjectWithKey(from, name)) {
const nextValue = from[name];

if (colIndex || colId) {
return extractCollectionItem(nextValue, {colIndex, colId});
}

return nextValue;
}
};

Expand Down
176 changes: 176 additions & 0 deletions src/modules/case.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ describe('fieldExtractor', () => {
expect(fieldValue).toBeUndefined();
});

test('should extract field as undefined when parent element is null', () => {
const aCase = {
data: {
level1: null
}
};

const fieldValue = fieldExtractor(aCase)('level1.level2');
expect(fieldValue).toBeUndefined();
});

test('should extract field as undefined when case has no data', () => {
const aCase = {};

Expand Down Expand Up @@ -114,6 +125,171 @@ describe('fieldExtractor', () => {
});
});

test('should extract simple collection item from case `data` using item index', () => {
const aCase = {
data: {
level1: {
level2: [
{id: '123', value: 'value1'},
{id: '456', value: 'value2'},
{id: '789', value: 'value3'},
],
}
}
};

const fieldValues = fieldExtractor(aCase)([
'level1.level2[2].value',
'level1.level2[1].value',
'level1.level2[0].value',
]);
expect(fieldValues).toEqual([
'value3',
'value2',
'value1',
]);
});

test('should extract complex collection item from case `data` using item index', () => {
const aCase = {
data: {
level1: {
level2: [
{id: '123', value: {key: 'value1'}},
{id: '456', value: {key: 'value2'}},
],
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[1].value.key');
expect(fieldValues).toEqual('value2');
});

test('should extract collection item as undefined when out of range', () => {
const aCase = {
data: {
level1: {
level2: [
{id: '123', value: 'value1'},
],
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[1].value');
expect(fieldValues).toBeUndefined();
});

test('should extract collection item as undefined when invalid index', () => {
const aCase = {
data: {
level1: {
level2: [
{id: '123', value: 'value1'},
],
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[a].value');
expect(fieldValues).toBeUndefined();
});

test('should extract collection item as undefined when not collection', () => {
const aCase = {
data: {
level1: {
level2: 'hello',
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[0].value');
expect(fieldValues).toBeUndefined();
});

test('should extract collection item as undefined when item malformed', () => {
const aCase = {
data: {
level1: {
level2: [true],
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[0].value');
expect(fieldValues).toBeUndefined();
});

test('should extract collection item as undefined when null', () => {
const aCase = {
data: {
level1: {
level2: [null],
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[0].value');
expect(fieldValues).toBeUndefined();
});

test('should extract simple collection item from case `data` using item ID', () => {
const aCase = {
data: {
level1: {
level2: [
{id: '123', value: 'value1'},
{id: '456', value: 'value2'},
{id: '789', value: 'value3'},
],
}
}
};

const fieldValues = fieldExtractor(aCase)([
'level1.level2[id:456].value',
'level1.level2[id:789].value',
'level1.level2[id:123].value',
]);
expect(fieldValues).toEqual([
'value2',
'value3',
'value1',
]);
});

test('should extract complex collection item from case `data` using item ID', () => {
const aCase = {
data: {
level1: {
level2: [
{id: '123', value: {key: 'value1'}},
],
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[id:123].value.key');
expect(fieldValues).toEqual('value1');
});

test('should extract collection item as undefined when invalid ID', () => {
const aCase = {
data: {
level1: {
level2: [
{id: '123', value: 'value1'},
],
}
}
};

const fieldValues = fieldExtractor(aCase)('level1.level2[id:456].value');
expect(fieldValues).toBeUndefined();
});

test('should throw error if provided path is not of a supported type', () => {
const aCase = {};

Expand Down