@ique/jql
v0.1.6
Published
JavaScript Object Query Language
Downloads
14
Readme
jql
JavaScript Query Language
TO DO
Project Stuff
- Get Babel working
- Write more tests
- Write some documentation
- Remove dependence on lodash
- OPTIMIZE
Handle Basic Top Level Select
This doesn't work...
const data = {
first: 'One',
second: 'Two',
third: 'Three',
fourth: 'Four',
}
const query = `
first,
third
`
Current implementation requires a top level key. Should be able to handle this case with another if statement looking for end of string or comma to parseBlock
.
Elevate Keys
Move the selected key up a level with ^
character. Think:
const query = `
1: {
first,
second: {
two,
four: {
^val
}
}
},
2: {
third
}
`;
would produce
const filteredData = {
1: {
first: 'One',
second: {
two: 'Two',
val: 'Four'
}
},
2: {
third: 'Tre'
}
};
May need to be done in another operation.
Alias Keys
Alias the selected keys in a similar fashion to a destructure ': newName'. Think:
const query = `
1: {
first,
second: {
two,
four: {
^val : four
}
}
},
2: {
third
}
`;
would produce
const filteredData = {
1: {
first: 'One',
second: {
two: 'Two',
four: 'Four'
}
},
2: {
third: 'Tre'
}
};
This could be tricky because of our dependence on the position of the :
.
Idea
"Query" or "Pick" from an object/map using a string template literal in the style of GraphQL.
Example:
Start with:
const data = {
1: {
first: 'One',
second: {
two: 'Two',
three: 'Three',
four: {
val: 'Four'
}
}
},
2: {
first: 'Uno',
second: 'Due',
third: 'Tre'
}
};
Query:
const query = `
1: {
first,
second: {
two,
four: {
val
}
}
},
2: {
third
}
`;
Call:
const result = jql(data, query);
Result:
const filteredData = {
1: {
first: 'One',
second: {
two: 'Two',
four: {
val: 'Four'
}
}
},
2: {
third: 'Tre'
}
};
Approach
Current approach is to convert the query into an array of selectors similar to the ones used in a lodash 'pick'.
const converted = [
'1.first',
'1.second',
'1.second.two',
'1.second.four.val',
'2.third'
]
This array would be filtered to remove duplicate higher level paths like:
const converted = [
'1.first',
'1.second.two',
'1.second.four.val',
'2.third'
]
And then be fed to lodash to select the keys and return the desired object.
Future
I'd like to not be dependent on lodash, so re-implementing the pick logic in this codebase and with extension in mind.
Thoughts About the Parsing Algorithm
The basic logic is something like this:
Input: a String Output: an array of strings that are object properties, with duplicates removed
- Initialize an empty array to hold the properties
- We start with the first property, since we know the input will always start with a property (see example data), and iterate through characters.
- While the character is not a ',' or a :, we keep adding characters to this property.
- If we reach a ',' we push the current working string (property) onto our properties array, and start a new property.
- If we reach a ':', then we know we have reached a property that itself is an object. So we call a function that takes the current property string as an argument (appending a '.' to the end since we know we are now inside a nested object), and the current character index + 1 (to skip the curly brace itself) as another argument, and then iterate through characters again, starting from the current character.
- If we reach a ',' we start a new property, meaning we go back to the one passed in to this instance of the function (which already has some value like '1.first.two', and start appending it again.
- If we reach a ':' again, the function calls itself, again passing in the current character index + 1 as the starting index, the current string we're building, etc.
Examples / working ideas (that will probably break your call stack)
const query = `
1: {
first,
second: {
two,
four: {
val
}
}
},
2: {
third
}
`;
// Maybe something like this?
const normalizedQuery = query.replace(/(\n|\s)/g, '');
const converted = [];
createPropertyString(normalizedQuery, '', 0);
const createPropertyString = (query, currentPropString, charPosition) => {
while (query[charPosition] !== ',' && query[charPosition] !== ':') {
currentPropString += query[charPosition];
charPosition++;
}
if (query[charPosition] === ',') {
converted.push(currentPropString);
createPropertyString(query.slice(charPosition + 1), '', 0);
} else if (query[charPosition] === ':') {
createPropertyString(query.slice(charPosition + 1), (currentPropString + '.'), 0);
}
return currentPropString;
};
// Or maybe something like this for a single section... the question is how to properly nest these and the iterate over
// the query as a whole? Do we go through and count the brackets for example, in order to split the string up at the
// commas in the correct place, then feed each section obtained that way into a recursive function like this or the above?
// (this doesn't work, btw, but seems like it could...)
const createPropertyString = (query, currentPropString, charPosition) => {
for (let i = charPosition; i < query.length; i++) {
if (query[charPosition] === ',' ) {
return currentPropString;
} else if (query[charPosition] === ':') {
createPropertyString(query.slice(charPosition + 1), (currentPropString += '.'), i);
} else {
currentPropString += query[charPosition];
}
}