0.1.6 • Published 5 years ago

@ique/jql v0.1.6

Weekly downloads
-
License
ISC
Repository
-
Last release
5 years ago

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];
    }
}
0.1.6

5 years ago

0.1.5

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago