gson-query
Advanced tools
Comparing version 3.0.1 to 4.1.0
@@ -1,70 +0,48 @@ | ||
/* eslint no-unused-vars: 0 */ | ||
const query = require("./run"); | ||
const { parse } = require("./parser"); | ||
const { run, VALUE_INDEX, POINTER_INDEX } = require("./interpreter"); | ||
/** | ||
* Returns the query results as an array or object, depending on its callback | ||
* | ||
* ## return type | ||
* | ||
* - get.ALL = 'all' returns all arguments of query callback [value, key, parent, pointer] | ||
* - get.POINTER = 'pointer' returns only the json pointers to the targets | ||
* - get.VALUE = 'value' Default. Returns only the matched value | ||
* - get.MAP = Returns an object with all available pointers and their data, like { pointer: value } | ||
* | ||
* @param {Mixed} obj | ||
* @param {Pointer} jsonPointer | ||
* @param {String} type - type of return value. Defaults to "value" | ||
* @return {Array|Object} containing result in specified format | ||
*/ | ||
function queryGet(obj, jsonPointer, type) { | ||
const matches = type === queryGet.MAP ? {} : []; | ||
const cb = getCbFactory(type, matches); | ||
query(obj, jsonPointer, cb); | ||
return matches; | ||
} | ||
const returnTypes = { | ||
value: r => r.map(e => e[VALUE_INDEX]), | ||
pointer: r => r.map(e => e[POINTER_INDEX]), | ||
all: r => r, | ||
map: r => { | ||
const map = {}; | ||
r.forEach(e => (map[e[POINTER_INDEX]] = e[VALUE_INDEX])); | ||
return map; | ||
} | ||
}; | ||
queryGet.ALL = "all"; | ||
queryGet.MAP = "map"; | ||
queryGet.POINTER = "pointer"; | ||
queryGet.VALUE = "value"; | ||
queryGet.getCbFactory = getCbFactory; | ||
Object.keys(returnTypes).forEach(prop => (get[prop.toUpperCase()] = prop)); | ||
function get(data, queryString, returnType = "value") { | ||
if (queryString == null) { | ||
return []; | ||
} | ||
function getCbFactory(type, matches) { | ||
if (typeof type === "function") { | ||
return function cb(value, key, obj, pointer) { | ||
matches.push(type(obj[key], key, obj, pointer)); | ||
}; | ||
queryString = queryString.replace(/(\/$)/g, ""); | ||
if (queryString === "") { | ||
queryString = "#"; | ||
} | ||
switch (type) { | ||
case queryGet.ALL: | ||
return function cbGetAll(value, key, obj, pointer) { | ||
matches.push([obj[key], key, obj, pointer]); | ||
}; | ||
const ast = parse(queryString); | ||
if (ast == null) { | ||
throw new Error(`empty ast for '${queryString}'`); | ||
} | ||
if (ast.rest !== "") { | ||
throw new Error(`Failed parsing queryString from: '${ast.rest}'`); | ||
} | ||
case queryGet.MAP: | ||
return function cbGetMap(value, key, obj, pointer) { | ||
matches[pointer] = value; | ||
}; | ||
case queryGet.POINTER: | ||
return function cbGetPointer(value, key, obj, pointer) { | ||
matches.push(pointer); | ||
}; | ||
const result = run(data, ast); | ||
if (returnTypes[returnType]) { | ||
return returnTypes[returnType](result); | ||
} else if (typeof returnType === "function") { | ||
return result.map(r => returnType(...r)); | ||
} | ||
case queryGet.VALUE: | ||
return function cbGetValue(value, key, obj, pointer) { | ||
matches.push(value); | ||
}; | ||
default: | ||
return function cbGetValue(value, key, obj, pointer) { | ||
matches.push(value); | ||
}; | ||
} | ||
return result; | ||
} | ||
module.exports = queryGet; | ||
module.exports = get; |
exports.get = require("./get"); | ||
exports.run = require("./run"); | ||
exports.delete = require("./delete"); | ||
exports.filter = require("./filter"); | ||
exports.pattern = require("./pattern"); | ||
exports.remove = require("./remove"); |
{ | ||
"name": "gson-query", | ||
"version": "3.0.1", | ||
"version": "4.1.0", | ||
"description": "json-pointer utilities for querying and transforming data", | ||
"main": "lib/index.js", | ||
"main": "dist/gson-query.js", | ||
"repository": { | ||
@@ -11,5 +11,7 @@ "type": "git", | ||
"scripts": { | ||
"dist": "rm -rf dist; webpack", | ||
"test": "mocha --recursive 'test/unit/**/*.test.js' -R spec; exit 0", | ||
"tdd": "watch 'npm run test' lib/ test/; exit 0", | ||
"lint": "eslint lib test", | ||
"prepublish": "npm run test & npm run lint & npm run dist", | ||
"coverage": "nyc npm run test --reporter=lcov", | ||
@@ -19,2 +21,3 @@ "debug": "devtool node_modules/mocha/bin/_mocha -qc -- --recursive test/unit/*.test.js" | ||
"dependencies": { | ||
"ebnf": "^1.6.3", | ||
"gson-conform": "^1.0.3", | ||
@@ -24,2 +27,5 @@ "gson-pointer": "^3.4.1" | ||
"devDependencies": { | ||
"@babel/core": "^7.6.2", | ||
"@babel/preset-env": "^7.6.2", | ||
"babel-loader": "^8.0.6", | ||
"chai": "^4.2.0", | ||
@@ -29,3 +35,6 @@ "eslint": "^6.4.0", | ||
"nyc": "^14.1.1", | ||
"watch": "^1.0.1" | ||
"uglifyjs-webpack-plugin": "^2.2.0", | ||
"watch": "^1.0.1", | ||
"webpack": "^4.41.0", | ||
"webpack-cli": "^3.3.9" | ||
}, | ||
@@ -32,0 +41,0 @@ "keywords": [ |
373
README.md
<h1 align="left"><img src="./docs/gson-query.png" width="256" alt="gson-query"></h1> | ||
**Query and transform your json data using an extended glob-pattern within browsers or nodejs** | ||
> gson-query lets you quickly select values, patterns or types from json-data. Its input requires a simple string, describing a concise query into your data | ||
> Query and transform your json data using an extended glob-pattern. This is a really helpful tool to quickly | ||
> | ||
> - fuzzy search json-data matching some search properties | ||
> - transform data with consistent structures | ||
> - extract information from json-data | ||
**npm package** `gson-query`. The command-line integration can be installed separately from [gson-query-cli](https://github.com/sagold/gson-query-cli) | ||
`npm install gson-query --save` | ||
and get it like | ||
`const query = require("gson-query");` | ||
The command-line integration can be installed separately by [gson-query-cli](https://github.com/sagold/gson-query-cli) | ||
- [Features](#features) | ||
- [Introduction](#quick-introduction) | ||
- [Breaking Changes](#breaking-changes) | ||
- [API](#api) | ||
- [query](#query) | ||
- [callback](#callback) | ||
- [query.run](#queryrun) | ||
- [query.get](#queryget) | ||
- [query.delete](#querydelete) | ||
- [query.pattern](#querypattern) | ||
- [About patterns](#about-patterns) | ||
- [Further examples](#further-examples) | ||
## Breaking Changes | ||
- with version `v3.0.0` (2019/09/26) | ||
- the syntax has changed to es6, which might require code transpilation | ||
- queries for root-pointer (`#`, `#/`, `/`) no callback root object with `(rootObject, null, null, "#")` | ||
- with `v2.0.0` a negated filter (lookahead), e.g. `*?valid:!true` will not return objects where `valid === undefined`. To match objects with missing properties you can still query them explicitly with `*?valid:!true||valid:undefined` | ||
## Features | ||
- [json-pointer](https://github.com/sagold/json-pointer) syntax `#/list/0/id` | ||
- glob-patterns for properties (`*`, `**`) | ||
- regex-support for properties `{any.*}` | ||
- pattern-support for inifinite recursion `/tree(/nodes/*)+/value` | ||
- or-patterns `/node((/left), (/right))` | ||
- finite search in circular-data `**` | ||
- lookahead-rules to test selected property `?property:value` and regex values `?property:{\d+}` | ||
- and typechecks `/value?:array` | ||
## Quick introduction | ||
**run** a callback-function on each match of your _query_ | ||
Basically, a **query** is a json-pointer, which describes a path of properties into the json-data | ||
```js | ||
query.run(data, "/server/*/services/*", callback); | ||
import { get } from "gson-query"; | ||
const input = { object: { a: { id: "id-a" }, b: { id: "id-b" } } }; | ||
const values = get(input, "/object/a/id"); // ["id-a"] | ||
``` | ||
a **callback** receives the following arguments | ||
```js | ||
/** | ||
* @param {Any} value - value of the matching query | ||
* @param {String} key - the property or index of the value | ||
* @param {Object|Array} parent - parent[key] === value | ||
* @param {String} jsonPointer - json-pointer in data, pointing to value | ||
*/ | ||
function callback(value, key, parent, jsonPointer) => { /* do sth */ } | ||
``` | ||
But each property may also be a glob-pattern or a regular expression: | ||
**get** matches in an array instead of running a callback | ||
`*` selects all direct children | ||
```js | ||
let results = query.get(data, "/server/*?state:critical", query.get.VALUE); // or POINTER or ALL | ||
const input = { object: { a: { id: "id-a" }, b: { id: "id-b" } } }; | ||
const values = get(input, "/object/*/id"); // ["id-a", "id-b"] | ||
``` | ||
which is the same as | ||
`**` selects all values | ||
```js | ||
let results = query.get(data, "/server/*/services/*", (value) => value); | ||
const input = { object: { a: { id: "id-a" }, b: { id: "id-b" } } }; | ||
const values = get(input, "/object/**/id"); | ||
// [ { a: { id: "id-a" }, b: { id: "id-b" } }, { id: "id-a" }, "id-a", { id: "id-b" }, "id-b" ] | ||
``` | ||
or quickly **delete** properties from your data | ||
`{}` calls a regular expression | ||
```js | ||
query.delete(data, "/server/*/services/{szm-.*}"); | ||
const input = { object: { a: { id: "id-a" }, b: { id: "id-b" } } }; | ||
const values = get(input, "/{obj.*}/{.*}/id"); // ["id-a", "id-b"] | ||
``` | ||
Use **patterns** to query patterns recursively | ||
**lookahead** rules are used to validate the current value based on its properties | ||
`?child` tests if a childProperty is defined | ||
```js | ||
query.pattern(data, "/node(/nodes/*)+/value"); | ||
``` | ||
const input = { object: { a: { id: "id-a" }, b: { id: "id-b" } } }; | ||
and to select multiple properties of an object or array: | ||
```js | ||
query.pattern(data, "/server((/store), (/{front-.*}))/services/*"); | ||
const values = get(input, "/object/*?id"); // [{ id: "id-a" }, { id: "id-b" }] | ||
``` | ||
`?child:value` tests if a childProperty matches a value | ||
## API | ||
```js | ||
const input = { object: { a: { id: "id-a" }, b: { id: "id-b" } } }; | ||
All examples import `const query = require("gson-query");` | ||
const values = get(input, "/object/*?id:id-b"); // [{ id: "id-b" }] | ||
``` | ||
### query | ||
lookahead rules can also be negated `?child:!value`, tested by regex `?child:{^re+}`, combined `?child&&other` or joined `?child||other`. Undefined may be tested with `?property:undefined`, per default `undefined` is excluded from matches. | ||
At first, **json-query** acts like a normal [**json-pointer**](https://github.com/sagold/json-pointer) | ||
```js | ||
let data = { | ||
"parent": { | ||
"child": { "id": "child-1" } | ||
} | ||
}; | ||
const result = query.get(data, "#/parent/child/id", query.get.VALUE); | ||
// result: | ||
[ | ||
"child-1" | ||
] | ||
``` | ||
**typechecks** can be used to query certain data-types | ||
But query also supports **glob-patterns** with `*`: | ||
`?:<type>`, where `<type>` may be any of `["boolean", "string", "number", "object", "array", "value"]` | ||
```js | ||
let data = { | ||
"parent": { | ||
"child": { "id": "child-1" } | ||
}, | ||
"neighbour": { | ||
"child": { "id": "child-2" } | ||
} | ||
}; | ||
const result = query.get(data, "#/*/child/id", query.get.VALUE); | ||
// result: | ||
[ | ||
"child-1", | ||
"child-2" | ||
] | ||
const input = { object: { a: { id: 33 }, b: { id: "id-b" } } }; | ||
const values = get(input, "/**?:string"); // ["id-b"] | ||
``` | ||
and **glob-patterns** with `**`: | ||
`?:value` will match all types except *objects* and *arrays* | ||
```js | ||
let data = { | ||
"parent": { | ||
"id": "parent", | ||
"child": {"id": "child-1"} | ||
}, | ||
"neighbour": { | ||
"child": {"id": "child-2"} | ||
} | ||
}; | ||
const result = query.get(data, "#/**/id", query.get.VALUE); | ||
// result: | ||
[ | ||
"parent", | ||
"child-1", | ||
"child-2" | ||
] | ||
const input = { object: { a: { id: 33 }, b: { id: "id-b" } } }; | ||
const values = get(input, "/**?:value"); // [33, "id-b"] | ||
``` | ||
or simply call `query.get(data, "#/**", query.get.VALUE)` to query the value of each property | ||
```js | ||
let data = { | ||
"parent": { | ||
"id": "parent", | ||
"child": { "id": "child-1" } | ||
} | ||
}; | ||
const result = query.get(data, "#/**/id", query.get.VALUE); | ||
// result: | ||
[ | ||
{ | ||
"id":"parent", | ||
"child": { "id":"child-1" } | ||
}, | ||
"parent", | ||
{ "id":"child-1" }, | ||
"child-1" | ||
] | ||
``` | ||
**patterns** can be used to combine queries into a single result (*OR*) and to build up results from recursive queries (*+*) | ||
To **filter** the matched objects, an object-query string may be appended on each single step: | ||
Queries can be grouped by parenthesis, where `/a/b/c = /a(/b)(/c) = /a(/b/c)`. | ||
`((/a), (/b))` resolves both queries on the previous result | ||
```js | ||
let data = { | ||
"parent": { | ||
"valid": true, | ||
"child": {"id": "child-1"} | ||
}, | ||
"neighbour": { | ||
"valid": false, | ||
"child": {"id": "child-2"} | ||
}, | ||
"dungeons": { | ||
"child": {"id": "child-3"} | ||
} | ||
}; | ||
let result = query.get(data, "#/**?valid:true&&ignore:undefined/child", query.get.VALUE); | ||
// same result with | ||
result = query.get(data, "#/**?valid:!false/child", query.get.VALUE); | ||
// result: | ||
[ | ||
{ | ||
"valid": true, | ||
"child": {"id": "child-1"} | ||
} | ||
] | ||
const input = { object: { a: { id: 33 }, b: { id: "id-b" } } }; | ||
const values = get(input, "/object((/a), (/b))"); // [{ id: 33 }, { id: "id-b" }] | ||
``` | ||
or match all objects that have a defined property _valid_ like `query.run(data, "#/**?valid", callback)`. | ||
and the result may be queried further | ||
```js | ||
let data = { | ||
"parent": { | ||
"valid": true, | ||
"child": {"id": "child-1"} | ||
}, | ||
"neighbour": { | ||
"valid": false, | ||
"child": {"id": "child-2"} | ||
}, | ||
"dungeons": { | ||
"child": {"id": "child-3"} | ||
} | ||
}; | ||
const result = query.get(data, "#/**?valid", query.get.VALUE); | ||
// result: | ||
[ | ||
{ | ||
"valid": true, | ||
"child": { | ||
"id": "child-1" | ||
} | ||
}, | ||
{ | ||
"valid": false, | ||
"child": { | ||
"id": "child-2" | ||
} | ||
} | ||
] | ||
get(input, "/object((/a), (/b))/id"); // [33, "id-b"] | ||
get(input, "/object((/a), (/b))/id?:number"); // [33] | ||
``` | ||
**regular expression** must be wrapped with `{.*}`: | ||
`(/a)+` will repeat the grouped query for all possible results | ||
```js | ||
let data = { | ||
"albert": {valid: true}, | ||
"alfred": {valid: false}, | ||
"alfons": {valid: true} | ||
const input = { | ||
id: 1, | ||
a: { // first iteration | ||
id: 2, | ||
a: { // second iteration | ||
id: 3 | ||
a: 4 // last iteration | ||
} | ||
} | ||
}; | ||
const result = query.get(data, "#/{al[^b]}?valid:true", query.get.POINTER); | ||
// result: | ||
[ | ||
"#/alfred" | ||
] | ||
const values = get(input, "/(/a)+"); // [{ id: 2, a: { id: 3, a: 4 } }, { id: 3, a: 4 }, 4] | ||
``` | ||
### query.run | ||
## Breaking Changes | ||
If you want a callback on each match use `query.run(data:object|array, query:string, callback:function):void` | ||
- with version `v4.0.0` (2019/10/01) | ||
- the api has been simplified to methods `query.get` and `query.delete` (removed `run` and `pattern`) | ||
- default package-entry is a es5-web-bundle | ||
- with version `v3.0.0` | ||
- the syntax has changed to es6, which might require code transpilation | ||
- queries for root-pointer (`#`, `#/`, `/`) now callback root object with `(rootObject, null, null, "#")` | ||
- with `v2.0.0` a negated filter (lookahead), e.g. `*?valid:!true` will not return objects where `valid === undefined`. To match objects with missing properties you can still query them explicitly with `*?valid:!true||valid:undefined` | ||
```js | ||
query.run(data, "#/**/*?valid", (value, key, parent, jsonPointer) => {}); | ||
``` | ||
## API | ||
### callback | ||
*gson-query* exposes to methods `get` and `remove` | ||
Each **callback** has the following signature | ||
`callback(value:any, key:string, parent:object|array, jsonPointer:string)` | ||
method | signature | description | ||
--------|-------------------------------------------------------------------|------------------------------ | ||
get | (input:any, query: string, returnType?:string\|function) | query data, returns results | ||
remove | (input:any, query: string, returnRemoved?:boolean) | delete query targets, returns input | ||
**get** | ||
per default, *get* returns a list of all values | ||
```js | ||
/** | ||
* @param {Any} value - value of the matching query | ||
* @param {String} key - the property or index of the value | ||
* @param {Object|Array} parent - parent[key] === value | ||
* @param {String} jsonPointer - json-pointer in data, pointing to value | ||
*/ | ||
function callback(value, key, parent, jsonPointer) => { /* do sth */ } | ||
import { get } from "gson-query"; | ||
const input = { object: { a: { id: 33 }, b: { id: "id-b" } } }; | ||
const values = get(input, "/**?:value"); // [33, "id-b"] | ||
``` | ||
Using the optional value `returnType` you can change the result type to the following options | ||
`["all", "value", "pointer", "map"]`. The string values can also be accessed as property on `get`: `get.ALL, get.VALUE, get.POINTER, get.MAP`: | ||
### query.get | ||
If you only require values or pointers, use `query.get(data:object|array, query:string, type:TYPE = "all")` to receive an Array or Object as result | ||
returnType | description | ||
------------|------------------------------------------------------------------ | ||
"value" | returns all matched values of the query `[33, "id-b"]` | ||
"pointer" | returns json-pointer to results `["#/object/a", "#/object/b"]` | ||
"map" | returns an pairs of `jsonPointer: resultValue` as an object | ||
"all" | returns a list, where each result is an array of `[value, keyToValue, parentObject, jsonPointer]` | ||
function | callback with `(value, keyToValue, parentObject, jsonPointer) => {}`. If a value is returned, the result will be replaced by the return-value | ||
```js | ||
// default: query.get.VALUES | ||
let arrayOfValues = query.get(data, "#/**/id", query.get.VALUE); | ||
// result: [value, value] | ||
import { get } from "gson-query"; | ||
const input = { object: { a: { id: 33 }, b: { id: "id-b" } } }; | ||
let arrayOfJsonPointers = query.get(data, "#/**/id", query.get.POINTER); | ||
// result: ["#/..", "#/..", ...] | ||
get(input, "/**?:value", get.VALUE); // [33, "id-b"] | ||
get(input, "/**?:value", get.POINTER); // ["#/object/a/id", "#/object/b/id"] | ||
get(input, "/**?:value", get.MAP); // { "#/object/a/id": 33, "#/object/b/id": "id-b" } | ||
let arrayOfAllFourArguments = query.get(data, "#/**/id", query.get.ALL); | ||
// result: [arguments, arguments], where arguments = 0:value 1:object 2:key 3:jsonPointer | ||
get(input, "/**?:value", get.ALL); | ||
// [ | ||
// [33, "id", { id: 33 }, "#/object/a/id"], | ||
// ["id-b", "id", { id: "id-b" }, "#/object/b/id"] | ||
// ] | ||
let mapOfPointersAndData = query.get(data, "#/**/id", query.get.MAP); | ||
// result: {"#/..": value, "#/..": value} | ||
let mapOfPointersAndData = query.get(data, "#/**/id", (val, key, parent, pointer) => `custom-${pointer}`); | ||
// result: ["custom-#/parent/child/id", "custom-#/neighbour/child/id", "custom-#/dungeons/child/id"] | ||
get(input, "/**?:value", (value, key, parent, pointer) => `custom-${pointer}`); | ||
// ["custom-#/object/a/id", "custom-#/object/b/id"] | ||
``` | ||
### query.delete | ||
**remove** deletes any match from the input data. | ||
Note: the input will be modified. If this is unwanted behaviour, copy your data up front. | ||
Multiple items on objects or in arrays may also be delete with `query.delete(data:object|array, query:string):void`: | ||
```js | ||
import { remove } from "gson-query"; | ||
const input = { object: { a: { id: 33 }, b: { id: "id-b" } } }; | ||
```js | ||
query.delete(data, "#/**/*/data"); | ||
remove(input, "/object/*/id"); // { object: { a: {}, b: {} } }; | ||
``` | ||
Per default, the input object is returned. Setting the optional argument `returnRemoved = true`, will return a list of the removed items | ||
### query.pattern | ||
The pattern-queries behave as the default `query.get` methods: | ||
```js | ||
import query from "gson-query"; | ||
import { remove } from "gson-query"; | ||
const input = { object: { a: { id: 33 }, b: { id: "id-b" } } }; | ||
// predefined callback | ||
const targets = query.pattern(data, "#/*(/node/*?valid)+/valid", query.get.POINTER); // return pointers | ||
const values = query.pattern(data, "#/*(/node/*?valid)+/valid", query.get.VALUES); // return values | ||
// ... | ||
// custom callback | ||
query.pattern(data, "#/*(/node/*?valid)+/valid", (value, key, parent, jsonPointer) => {}); | ||
remove(input, "/object/*/id", true); // [ 33, "id-b" ] | ||
``` | ||
## About patterns | ||
Pattern-queries enable selection of recursive patterns and offer a way to build up a collection of data for further filterung. A pattern uses brackets `()` to identify repeatable structures and offers multiple selections for the same data-entry. | ||
@@ -339,7 +250,7 @@ | ||
const result = query.pattern(data, "#/tree((/left),(/right))*/id"); | ||
const result = get(data, "#/tree((/left),(/right))*/id"); | ||
// ["1", "2", "3", "4"] | ||
``` | ||
**Note** that each pattern-queries are resovled using `query.get` and thus support all mentioned features. | ||
**Note** that each pattern-queries is resovled using `query.get` and thus supports all mentioned features. | ||
@@ -380,5 +291,3 @@ One use-case for pattern-queries can be found in json-schema specification. Any definition in `#/defs` may reference itself or be referenced circular. A linear query cannot describe the corresponding data, but pattern-queries might be sufficient. | ||
- [query.delete](https://github.com/sagold/json-query/blob/master/test/unit/queryDelete.test.js) | ||
- [query.get](https://github.com/sagold/json-query/blob/master/test/unit/queryGet.test.js) | ||
- [query.query](https://github.com/sagold/json-query/blob/master/test/unit/query.test.js) | ||
- [query.pattern](https://github.com/sagold/json-query/blob/master/test/unit/pattern.test.js) | ||
- [query.delete](https://github.com/sagold/json-query/blob/master/test/unit/delete.test.js) | ||
- [query.get](https://github.com/sagold/json-query/blob/master/test/unit/get.test.js) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
49027
3
11
14
395
292
1
+ Addedebnf@^1.6.3
+ Addedebnf@1.9.1(transitive)