diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8581f5a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,65 @@ +# Contributing to JSON P3 + +Hi. Your contributions and questions are always welcome. Feel free to ask questions, report bugs or request features on the [issue tracker](https://github.com/jg-rp/json-p3/issues) or on [Github Discussions](https://github.com/jg-rp/json-p3/discussions). + +**Table of contents** + +- [Development](#development) +- [Documentation](#documentation) + +## Development + +The [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite) is included as a git [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules). Clone the JSON P3 git repository and initialize the CTS submodule. + +```shell +$ git clone git@github.com:jg-rp/json-p3.git +$ cd json-p3 +$ git submodule update --init +``` + +We use [npm](https://docs.npmjs.com/cli/v10/commands/npm) to mange packages and run scripts during development. Install development dependencies with: + +```shell +$ npm install --production=false +``` + +And run tests with the _test_ script. + +```shell +$ npm test +``` + +Check for linting errors with the _lint_ script. + +```shell +$ npm run lint +``` + +And check for typing errors with the _type-check_ script. + +```shell +$ npm run type-check +``` + +Generate an HTML test coverage report with the _coverage_ script. + +```shell +$ npm run coverage +``` + +Then open `coverage/index.html` in your browser. + +Build distribution bundles and generate `.ts` files with the _build_ script. This will write bundles to the `dist/` folder. + +```shell +$ npm run build +``` + +## Documentation + +[Documentation](https://jg-rp.github.io/json-p3/) is built with [Docusaurus](https://docusaurus.io/). Find its source in the `docs/` folder of the main branch. Start the Docusaurus development server from within the `docs/` folder. + +```shell +$ cd docs +$ npm run start +``` diff --git a/README.md b/README.md index 9e1b19a..621f3b0 100644 --- a/README.md +++ b/README.md @@ -19,101 +19,6 @@ JSONPath, JSON Patch and JSON Pointer for JavaScript. --- -**Table of Contents** - -- [Install](#install) -- [JSONPath](#jsonpath) -- [JSON Pointer](#json-pointer) -- [JSON Patch](#json-patch) - -## Install - -### Node.js - -Use npm: - -```console -npm install --save json-p3 -``` - -Or yarn: - -```console -yarn add json-p3 -``` - -Or pnpm: - -```console -pnpm add json-p3 -``` - -And use ES modules: - -```javascript -import { query } from "json-p3"; - -const data = { - players: [{ name: "Sue" }, { name: "John" }, { name: "Sally" }], - visitors: [{ name: "Brian" }, { name: "Roy" }], -}; - -const nodes = query("$..name", data); -console.log(nodes.values()); -// [ 'Sue', 'John', 'Sally', 'Brian', 'Roy' ] -``` - -Or CommonJS modules: - -```javascript -const { query } = require("json-p3"); - -const data = { - players: [{ name: "Sue" }, { name: "John" }, { name: "Sally" }], - visitors: [{ name: "Brian" }, { name: "Roy" }], -}; - -const nodes = query("$..name", data); -console.log(nodes.values()); -// [ 'Sue', 'John', 'Sally', 'Brian', 'Roy' ] -``` - -### Browser - -Download and include JSON P3 in a script tag: - -```html - - -``` - -Or use a CDN: - -```html - - -``` - -## JSONPath - -Retrieve values from JSON-like data using JSONPath query expressions. We strictly follow standards described in the [IETF JSONPath draft](https://datatracker.ietf.org/doc/html/draft-ietf-jsonpath-base-20). - ```javascript import { jsonpath } from "json-p3"; @@ -130,218 +35,33 @@ const nodes = jsonpath.query("$.users[?@.score < 100].name", data); console.log(nodes.values()); // [ 'John', 'Sally', 'Jane' ] ``` -The result of `jsonpath.query()` is an instance of `JSONPathNodeList`. That is a list of `JSONPathNode` objects, one node for each value in the target JSON document matching the query. Each node has a: +## Links -- `value` - The value found in the target JSON document. This could be an array, object or primitive value. -- `location` - An array of property names and array indices that were required to reach the node's value in the target JSON document. -- `path` - The normalized JSONPath to this node in the target JSON document. +- Docs: https://jg-rp.github.io/json-p3/ +- Install: https://jg-rp.github.io/json-p3/#install +- JSONPath syntax: https://jg-rp.github.io/json-p3/guides/jsonpath-syntax +- API reference: https://jg-rp.github.io/json-p3/api +- Change log: https://github.com/jg-rp/json-p3/blob/main/CHANGELOG.md +- NPM: https://www.npmjs.com/package/json-p3 +- Issue tracker: https://github.com/jg-rp/json-p3/issues -Use `JSONPathNodeList.paths()` to retrieve all node paths. +## Bundles -```javascript -// .. continued from above -console.log(nodes.paths()); -``` +JSON P3 is written in TypeScript, compiled to JavaScript using [Babel](https://babeljs.io/), and bundled using [Rollup](https://rollupjs.org/introduction/). The following, included bundles target `defaults, maintained node version`, as defined by [Browserslist](https://browsersl.ist/#q=defaults%2C+maintained+node+versions). -**Output:** +JSON P3 has zero runtime dependencies. -```plain -[ - "$['users']['1']['name']", - "$['users']['2']['name']", - "$['users']['3']['name']" -] -``` +| Bundle | Description | +| --------------------- | -------------------------------------------------------------------------- | +| `json-p3.cjs.js` | A CommonJS formatted bundle. | +| `json-p3.esm.js` | An ECMAScript module formatted bundle. | +| `json-p3-iife.js` | A bundle formatted as an Immediately Invoked Function Expression. | +| `json-p3-iife.min.js` | A minified bundle formatted as an Immediately Invoked Function Expression. | -And `JSONPathNodeList.locations()` to get an array of node locations. +## Contributing -```javascript -// .. continued from above -console.log(nodes.locations()); -``` +Please see [Contributing to JSON P3](https://github.com/jg-rp/json-p3/blob/main/CONTRIBUTING.md) -**Output:** +## License -```plain -[ - [ 'users', 1, 'name' ], - [ 'users', 2, 'name' ], - [ 'users', 3, 'name' ] -] -``` - -`JSONPathNodeList` objects are iterable too. - -```javascript -// .. continued from above -for (const node of nodes) { - console.log(`${node.value} @ ${node.path}`); -} -``` - -**Output:** - -```plain -John @ $['users'][1]['name'] -Sally @ $['users'][2]['name'] -Jane @ $['users'][3]['name'] -``` - -You can also compile a JSONPath query for repeated use against different data. - -```javascript -import { jsonpath } from "json-p3"; - -const data = { - users: [ - { name: "Sue", score: 100 }, - { name: "John", score: 86 }, - { name: "Sally", score: 84 }, - { name: "Jane", score: 55 }, - ], -}; - -const path = jsonpath.compile("$.users[?@.score < 100].name"); -const nodes = path.query(data); -console.log(nodes.values()); // [ 'John', 'Sally', 'Jane' ] -``` - -## JSON Pointer - -Identify a single value in JSON-like data, as per RFC 6901. Use `jsonpointer.resolve()` to retrieve the value. - -```javascript -import { jsonpointer } from "json-p3"; - -const data = { - users: [ - { name: "Sue", score: 100 }, - { name: "John", score: 86 }, - { name: "Sally", score: 84 }, - { name: "Jane", score: 55 }, - ], -}; - -const rv = jsonpointer.resolve("/users/1", data); -console.log(rv); // { name: 'John', score: 86 } -``` - -If the pointer can't be resolved against the argument JSON value, one of `JSONPointerIndexError`, `JSONPointerKeyError` or `JSONPointerTypeError` is thrown. All three exceptions inherit from `JSONPointerResolutionError`. - -```javascript -// .. continued from above -const rv = jsonpointer.resolve("/users/1/age", data); -// JSONPointerKeyError: no such property ("/users/1/age") -``` - -A fallback value can be given as a third argument, which will be returned in the event of a `JSONPointerResolutionError`. - -```javascript -// .. continued from above -const rv = jsonpointer.resolve("/users/1/age", data, -1); -console.log(rv); // -1 -``` - -You can also create an instance of `JSONPointer` then resolve it against different data. - -```javascript -import { JSONPointer } from "json-p3"; - -const someData = { - users: [ - { name: "Sue", score: 100 }, - { name: "John", score: 86 }, - { name: "Sally", score: 84 }, - ], -}; - -const otherData = { - users: [{ name: "Brian" }, { name: "Roy" }], -}; - -const pointer = new JSONPointer("/users/1"); -console.log(pointer.resolve(someData)); // { name: 'John', score: 86 } -console.log(pointer.resolve(otherData)); // { name: 'Roy' } -``` - -We also support [Relative JSON Pointers](https://www.ietf.org/id/draft-hha-relative-json-pointer-00.html) via the `to(rel)` method of `JSONPointer`, where `rel` is a relative JSON pointer string, and a new `JSONPointer` is returned. - -```javascript -import { JSONPointer } from "json-p3"; - -const data = { foo: { bar: [1, 2, 3], baz: [4, 5, 6] } }; -const pointer = new JSONPointer("/foo/bar/2"); - -console.log(pointer.resolve(data)); // 3 -console.log(pointer.to("0-1").resolve(data)); // 2 -console.log(pointer.to("2/baz/2").resolve(data)); // 6 -``` - -## JSON Patch - -Apply a JSON Patch ([RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902)) to some data. A JSON Patch defines update operation to perform on a JSON document. **Data is modified in place.**. - -```javascript -import { jsonpatch } from "json-p3"; - -const ops = [ - { op: "add", path: "/some/foo", value: { foo: {} } }, - { op: "add", path: "/some/foo", value: { bar: [] } }, - { op: "copy", from: "/some/other", path: "/some/foo/else" }, - { op: "add", path: "/some/foo/bar/-", value: 1 }, -]; - -const data = { some: { other: "thing" } }; -jsonpatch.apply(ops, data); -console.log(data); -// { some: { other: 'thing', foo: { bar: [Array], else: 'thing' } } } -``` - -Use the `JSONPatch` class to create a patch for repeated application. - -```javascript -import { JSONPatch } from "json-p3"; - -const patch = new JSONPatch([ - { op: "add", path: "/some/foo", value: { foo: {} } }, - { op: "add", path: "/some/foo", value: { bar: [] } }, - { op: "copy", from: "/some/other", path: "/some/foo/else" }, - { op: "add", path: "/some/foo/bar/-", value: 1 }, -]); - -const data = { some: { other: "thing" } }; -patch.apply(data); -console.log(data); -// { some: { other: 'thing', foo: { bar: [Array], else: 'thing' } } } -``` - -`JSONPatch` also offers a builder API for constructing JSON patch documents. We use strings as JSON Pointers in this example, but existing `JSONPointer` objects are OK too. - -```javascript -import { JSONPatch } from "json-p3"; - -const data = { some: { other: "thing" } }; - -const patch = new JSONPatch() - .add("/some/foo", { foo: [] }) - .add("/some/foo", { bar: [] }) - .copy("/some/other", "/some/foo/else") - .add("/some/foo/bar/-", "/some/foo/else"); - -patch.apply(data); -console.log(JSON.stringify(data, undefined, " ")); -``` - -**Output:** - -```json -{ - "some": { - "other": "thing", - "foo": { - "bar": ["/some/foo/else"], - "else": "thing" - } - } -} -``` +`json-p3` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.