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) {
this.data = this.data.filter(item => func(item[key]))
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':
this.data = this.data.filter(item => item[key] === term)
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 regFind = new RegExp(term, 'gi')
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) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
})
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,102 +10,233 @@ test('should not modify passed data without chain alterations', () => {
expect(query).toMatchObject(TestData)
})
test('should paginate with default params', () => {
let query = new Query(TestData)
.paginate()
.results
describe('.paginate()', () => {
test('with a flat array', () => {
let query = new Query(arrayOf100Things)
.paginate()
.results
expect(query.length).toBe(9)
expect(query.length).toBe(10)
})
test('using default params', () => {
let query = new Query(TestData)
.paginate()
.results
expect(query.length).toBe(9)
})
test('with custom page length, first page', () => {
let query = new Query(TestData)
.paginate(1, 3)
.results
expect(query.length).toBe(3)
expect(query[0].name).toBe('Haynes Meadows')
})
test('with custom page length, second page', () => {
let query = new Query(TestData)
.paginate(2, 3)
.results
expect(query.length).toBe(3)
expect(query[0].name).toBe('Howard Buckley')
})
})
test('should paginate with custom page length', () => {
let query = new Query(TestData)
.paginate(1, 3)
.results
describe('.search()', () => {
test('partial string in flat array of strings', () => {
let query = new Query(['bar', 'foo', 'foobar', 'foofoobar'])
.search('foo')
.results
expect(query.length).toBe(3)
expect(query[0].name).toBe('Haynes Meadows')
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('steele', 'name')
.results
expect(query.length).toBe(2)
})
test('by boolean isActive', () => {
let query = new Query(TestData)
.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()
})
})
test('should paginate to second page with custom page length', () => {
let query = new Query(TestData)
.paginate(2, 3)
.results
describe('.sort()', () => {
test('with flat array', () => {
let alphabetical = (a, b) => a > b
expect(query.length).toBe(3)
expect(query[0].name).toBe('Howard Buckley')
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')
})
})
test('should search by boolean isActive', () => {
let query = new Query(TestData)
.search('isActive', true)
.results
describe('.sortBy()', () => {
test('by boolean', () => {
let query = new Query(TestData)
.sortBy('isActive')
.results
expect(query.length).toBe(4)
expect(query[0].name).toBe('Katelyn Steele')
})
test('by number', () => {
let query = new Query(TestData)
.sortBy('netWorth')
.results
expect(query[0].name).toBe('Howard Buckley') // Negative
expect(query[1].name).toBe('Natalia Petty') // 0
expect(query[query.length - 1].name).toBe('Newman Mays') // Richest
})
test('by string', () => {
let query = new Query(TestData)
.sortBy('name')
.results
expect(query[0].name).toBe('Dudley Conner')
})
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()
})
})
test('should search by name', () => {
let query = new Query(TestData)
.search('name', 'steele')
.results
describe('.filter()', () => {
test('with a flat array', () => {
let isEven = a => !(a % 2)
expect(query.length).toBe(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)
.filter(isAgeOver33)
.results
expect(query[0].name).toBe('Howard Buckley')
})
})
test('should sort by boolean isActive', () => {
let query = new Query(TestData)
.sort('isActive')
.results
describe('.filterBy()', () => {
test('by key', () => {
let isNumGT33 = num => num > 33
expect(query[0].name).toBe('Katelyn Steele')
let query = new Query(TestData)
.filterBy('age', isNumGT33)
.results
expect(query[0].name).toBe('Howard Buckley')
})
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()
})
})
test('should sort by number netWorth', () => {
let query = new Query(TestData)
.sort('netWorth')
.results
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[0].name).toBe('Howard Buckley') // Negative
expect(query[1].name).toBe('Natalia Petty') // 0
expect(query[query.length - 1].name).toBe('Newman Mays') // Richest
})
test('should sort by string name', () => {
let query = new Query(TestData)
.sort('name')
.results
expect(query[0].name).toBe('Dudley Conner')
})
test('should filter', () => {
let isAgeOver33 = a => a.age > 33
let query = new Query(TestData)
.filter(isAgeOver33)
.results
expect(query[0].name).toBe('Howard Buckley')
})
test('should filter by key', () => {
let isNumGT33 = num => num > 33
let query = new Query(TestData)
.filterBy('age', isNumGT33)
.results
expect(query[0].name).toBe('Howard Buckley')
})
test('should chain everything together', () => {
let query = new Query(TestData)
.search('isActive', true)
.sort('name')
.paginate(1, 2)
.results
expect(query.length).toBe(2)
expect(query[0].name).toBe('Dudley Conner')
expect(query[query.length - 1].name).toBe('Haynes Meadows')
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(true, 'isActive')
.sortBy('name')
.paginate(1, 2)
.results
expect(query.length).toBe(2)
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"