Compare commits

..

2 commits

Author SHA1 Message Date
sharpshark28
48c87a946f Flat array support 2018-03-18 17:46:11 -05:00
sharpshark28
c30ed398e3 1.1.3 2018-03-18 17:34:54 -05:00
5 changed files with 271 additions and 133 deletions

24
LICENSE
View file

@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

View file

@ -2,34 +2,38 @@
[![Build Status](https://travis-ci.org/sharpshark28/json-query-chain.svg?branch=master)](https://travis-ci.org/sharpshark28/json-query-chain) [![npm version](https://badge.fury.io/js/json-query-chain.svg)](https://badge.fury.io/js/json-query-chain) ![Code Coverage](coverage.svg) [![Maintainability](https://api.codeclimate.com/v1/badges/4dc20d8b5e6a7334044d/maintainability)](https://codeclimate.com/github/sharpshark28/json-query-chain/maintainability)
Chain queries onto POJOs to return precise results.
Chain queries onto arrays and arrays of objects to return precise results. See [example usages in the tests](./index.test.js) running on [this test data](./testdata.json).
## Usage
```javascript
import Query from 'json-query-chain';
let myQ = new Query(someJsonData)
let query = new Query(someJsonData)
.search('isActiveUser', true)
.results;
console.log('Results: ', query);
```
### Chainable Methods
#### Search
Currently supports booleans and strings. (See [#1](https://github.com/sharpshark28/json-query-chain/issues/1) for Integer Support)
##### By Boolean
Acts as a smart filter returning only elements who's key matches the expected result.
```javascript
.search('isActiveUser', true)
.search(true, 'isActiveUser')
```
##### By String
Sorts and filters out any elements in the array not matching the requested value while attempting to raise the best results to the top (most frequent number of occurrences).
```javascript
.search('name', 'steele')
.search('steele', 'name')
```
#### Filter
@ -42,28 +46,42 @@ Simpler version of search using a custom function in the chain.
##### By Key
Like `.filter()`, but narrowed down by key.
```javascript
.filterBy('age', x => x >= 21)
```
#### Sort
A chainable version of javascript's built in array sort.
```
.sort((a, b) => a > b)
```
##### By Boolean
Abstracted sort by matching a key to a boolean.
```javascript
.sort('isActiveUser', true)
.sortBy('isActiveUser')
```
##### By String
Sorts alphabetically based on key.
```javascript
.sort('name')
.sortBy('name')
```
##### By Number
Sorts by ascending numerical order based on key.
```javascript
.sort('netWorth')
.sortBy('netWorth')
```
#### Pagination
@ -74,7 +92,7 @@ Page 1 with 5 results per page.
.paginate(1, 5)
```
Page 2 wtih default of 10 results per page.
Page 2 with default of 10 results per page.
```javascript
.paginate(2)

View file

@ -1,9 +1,6 @@
module.exports = class Query {
constructor (data) {
this.data = data.map(item => {
item.sortScore = 0
return item
})
this.data = data
}
get results () {
@ -16,33 +13,47 @@ module.exports = class Query {
}
filterBy (key, func) {
if (this.data.length && typeof this.data[0] === 'object') {
this.data = this.data.filter(item => func(item[key]))
} else console.warn('Attempted to use filterBy with a flat array')
return this
}
search (key, term, score = 0) {
search (term, key) {
switch (typeof term) {
case 'boolean':
if (this.data.length && typeof this.data[0] === 'object') {
this.data = this.data.filter(item => item[key] === term)
} else console.warn('Attempted to use search by boolean with a flat array')
break
case 'string':
this.data = this.data.filter(item => {
let regFind = new RegExp(term, 'gi')
let termMatches = (item[key].match(regFind) || []).length
item.sortScore += termMatches
return termMatches
})
let getMatches = item => {
if (typeof item === 'string') {
return (item.match(regFind) || []).length
} else {
return (item[key].match(regFind) || []).length
}
}
this.data = [...this.data].sort(getMatches).filter(getMatches)
break
}
return this
}
sort (key = 'sortScore') {
this.data = this.data.sort((a, b) => {
sort (func) {
this.data = [...this.data].sort(func)
return this
}
sortBy (key) {
if (this.data.length && typeof this.data[0] === 'object') {
this.data = [...this.data].sort((a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
})
} else console.warn('Attempted to use sortBy with a flat array. Try .sort(func) instead.')
return this
}

View file

@ -1,6 +1,8 @@
const Query = require('./index')
const TestData = require('./testdata.json')
const arrayOf100Things = [...Array(100).keys()]
test('should not modify passed data without chain alterations', () => {
let query = new Query(TestData)
.results
@ -8,7 +10,16 @@ test('should not modify passed data without chain alterations', () => {
expect(query).toMatchObject(TestData)
})
test('should paginate with default params', () => {
describe('.paginate()', () => {
test('with a flat array', () => {
let query = new Query(arrayOf100Things)
.paginate()
.results
expect(query.length).toBe(10)
})
test('using default params', () => {
let query = new Query(TestData)
.paginate()
.results
@ -16,7 +27,7 @@ test('should paginate with default params', () => {
expect(query.length).toBe(9)
})
test('should paginate with custom page length', () => {
test('with custom page length, first page', () => {
let query = new Query(TestData)
.paginate(1, 3)
.results
@ -25,7 +36,7 @@ test('should paginate with custom page length', () => {
expect(query[0].name).toBe('Haynes Meadows')
})
test('should paginate to second page with custom page length', () => {
test('with custom page length, second page', () => {
let query = new Query(TestData)
.paginate(2, 3)
.results
@ -33,34 +44,96 @@ test('should paginate to second page with custom page length', () => {
expect(query.length).toBe(3)
expect(query[0].name).toBe('Howard Buckley')
})
test('should search by boolean isActive', () => {
let query = new Query(TestData)
.search('isActive', true)
.results
expect(query.length).toBe(4)
})
test('should search by name', () => {
describe('.search()', () => {
test('partial string in flat array of strings', () => {
let query = new Query(['bar', 'foo', 'foobar', 'foofoobar'])
.search('foo')
.results
expect(query[0]).toBe('foofoobar')
expect(query).toContain('foo')
expect(query).not.toContain('bar')
})
test('by name with value/key', () => {
let query = new Query(TestData)
.search('name', 'steele')
.search('steele', 'name')
.results
expect(query.length).toBe(2)
})
test('should sort by boolean isActive', () => {
test('by boolean isActive', () => {
let query = new Query(TestData)
.sort('isActive')
.search(true, 'isActive')
.results
expect(query.length).toBe(4)
})
test('by boolean isActive, false', () => {
let query = new Query(TestData)
.search(false, 'isActive')
.results
expect(query.length).toBe(5)
})
test('warn when searching a flat array of booleans', () => {
let consoleWarnSpy = jest.spyOn(global.console, 'warn')
consoleWarnSpy.mockImplementation(() => {})
let query = new Query(arrayOf100Things)
.search(true, 'N/A')
.results
expect(query).toEqual(arrayOf100Things)
expect(consoleWarnSpy).toHaveBeenCalled()
consoleWarnSpy.mockReset()
consoleWarnSpy.mockRestore()
})
})
describe('.sort()', () => {
test('with flat array', () => {
let alphabetical = (a, b) => a > b
let query = new Query(['foo', 'bar', 'foobar'])
.sort(alphabetical)
.results
expect(query[0]).toBe('bar')
expect(query[1]).toBe('foo')
expect(query[2]).toBe('foobar')
})
test('with an array of objects', () => {
let alphabetical = (a, b) => a.name > b.name
let query = new Query(TestData)
.sort(alphabetical)
.results
expect(query[0].name).toBe('Dudley Conner')
expect(query[query.length - 1].name).toBe('Wade Steele')
})
})
describe('.sortBy()', () => {
test('by boolean', () => {
let query = new Query(TestData)
.sortBy('isActive')
.results
expect(query[0].name).toBe('Katelyn Steele')
})
test('should sort by number netWorth', () => {
test('by number', () => {
let query = new Query(TestData)
.sort('netWorth')
.sortBy('netWorth')
.results
expect(query[0].name).toBe('Howard Buckley') // Negative
@ -68,15 +141,43 @@ test('should sort by number netWorth', () => {
expect(query[query.length - 1].name).toBe('Newman Mays') // Richest
})
test('should sort by string name', () => {
test('by string', () => {
let query = new Query(TestData)
.sort('name')
.sortBy('name')
.results
expect(query[0].name).toBe('Dudley Conner')
})
test('should filter', () => {
test('warn when using sortBy with a flat array', () => {
let consoleWarnSpy = jest.spyOn(global.console, 'warn')
consoleWarnSpy.mockImplementation(() => {})
let query = new Query(arrayOf100Things)
.sortBy('N/A')
.results
expect(query).toEqual(arrayOf100Things)
expect(consoleWarnSpy).toHaveBeenCalled()
consoleWarnSpy.mockReset()
consoleWarnSpy.mockRestore()
})
})
describe('.filter()', () => {
test('with a flat array', () => {
let isEven = a => !(a % 2)
let query = new Query(arrayOf100Things)
.filter(isEven)
.results
expect(query).toContain(2)
expect(query).not.toContain(1)
})
test('with an array of objects', () => {
let isAgeOver33 = a => a.age > 33
let query = new Query(TestData)
@ -85,8 +186,10 @@ test('should filter', () => {
expect(query[0].name).toBe('Howard Buckley')
})
})
test('should filter by key', () => {
describe('.filterBy()', () => {
test('by key', () => {
let isNumGT33 = num => num > 33
let query = new Query(TestData)
@ -96,10 +199,39 @@ test('should filter by key', () => {
expect(query[0].name).toBe('Howard Buckley')
})
test('should chain everything together', () => {
test('warn when using filterBy with a flat array', () => {
let consoleWarnSpy = jest.spyOn(global.console, 'warn')
consoleWarnSpy.mockImplementation(() => {})
let query = new Query(arrayOf100Things)
.filterBy('N/A', () => {})
.results
expect(query).toEqual(arrayOf100Things)
expect(consoleWarnSpy).toHaveBeenCalled()
consoleWarnSpy.mockReset()
consoleWarnSpy.mockRestore()
})
})
describe('chaining everything together', () => {
test('with a flat array', () => {
let query = new Query(['bar', 'foo', 'foobar', 'foofoobar'])
.search('foo')
.sort()
.paginate(1, 2)
.results
expect(query.length).toBe(2)
expect(query[0]).toBe('foo')
expect(query[1]).toBe('foobar')
})
test('with an array of objects', () => {
let query = new Query(TestData)
.search('isActive', true)
.sort('name')
.search(true, 'isActive')
.sortBy('name')
.paginate(1, 2)
.results
@ -107,3 +239,4 @@ test('should chain everything together', () => {
expect(query[0].name).toBe('Dudley Conner')
expect(query[query.length - 1].name).toBe('Haynes Meadows')
})
})

View file

@ -12,7 +12,7 @@
"type": "git",
"url": "git+ssh://git@github.com/sharpshark28/json-query-chain.git"
},
"author": "Ava Wroten <ava@wroten.me>",
"author": "Joe Wroten <joe@wroten.me>",
"license": "ISC",
"bugs": {
"url": "https://github.com/sharpshark28/json-query-chain/issues"