Skip to content

Commit

Permalink
Implement JSONPath.match()
Browse files Browse the repository at this point in the history
  • Loading branch information
jg-rp committed Sep 20, 2023
1 parent 3ea842a commit 929e49a
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 8 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

**Features**

- Implement [Relative JSON Pointers](https://www.ietf.org/id/draft-hha-relative-json-pointer-00.html). Use the `to(rel)` method of `JSONPointer`, where `rel` is a relative JSON pointer string and a new `JSONPointer` is returned.
- Implemented [Relative JSON Pointers](https://www.ietf.org/id/draft-hha-relative-json-pointer-00.html). Use the `to(rel)` method of `JSONPointer`, where `rel` is a relative JSON pointer string and a new `JSONPointer` is returned.
- Guard against recursive data structures by implementing the `JSONPathEnvironment.maxRecursionDepth` option. When using the recursive descent selector (`..`), if the maximum recursion depth is reached, a `JSONPathRecursionLimitError` is thrown.
- Added `JSONPathEnvironment.match()` and `JSONPath.match()`, which returns a `JSONPathNode` for the first value matching a query, or `undefined` if there are no matches.

# Version 0.1.1

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"build": "npm run build:types && npm run build:js",
"type-check": "tsc --noEmit",
"coverage": "jest --collectCoverage",
"clean": "rm -rf ./lib ./dist ./coverage",
"clean": "rm -rf ./lib ./dist ./coverage ./docs/docs/api",
"lint": "eslint src tests --ext .js,.jsx,.ts,.tsx"
},
"browserslist": [
Expand Down
15 changes: 13 additions & 2 deletions src/path/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Match as MatchFilterFunction } from "./functions/match";
import { Search as SearchFilterFunction } from "./functions/search";
import { Value as ValueFilterFunction } from "./functions/value";
import { tokenize } from "./lex";
import { JSONPathNodeList } from "./node";
import { JSONPathNode, JSONPathNodeList } from "./node";
import { Parser } from "./parse";
import { JSONPath } from "./path";
import { Token, TokenStream } from "./token";
Expand Down Expand Up @@ -128,7 +128,18 @@ export class JSONPathEnvironment {
return this.compile(path).query(value);
}

// TODO: match(path, value): boolean
/**
* Return a {@link JSONPathNode} instance for the first object found in
* _value_ matching _path_.
*
* @param path - A JSONPath query.
* @param value - JSON-like data to which the query _path_ will be applied.
* @returns The first node in _value_ matching _path_, or `undefined` if
* there are no matches.
*/
public match(path: string, value: JSONValue): JSONPathNode | undefined {
return this.compile(path).match(value);
}

protected setupFilterFunctions(): void {
this.functionRegister.set("count", new CountFilterFunction());
Expand Down
18 changes: 17 additions & 1 deletion src/path/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JSONValue } from "../types";
import { JSONPathEnvironment } from "./environment";
import { JSONPathNodeList } from "./node";
import { JSONPathNode, JSONPathNodeList } from "./node";
import { JSONPath } from "./path";

export { JSONPathEnvironment } from "./environment";
Expand Down Expand Up @@ -64,3 +64,19 @@ export function query(path: string, value: JSONValue): JSONPathNodeList {
export function compile(path: string): JSONPath {
return DEFAULT_ENVIRONMENT.compile(path);
}

/**
* Return a {@link JSONPathNode} instance for the first object found in
* _value_ matching _path_.
*
* @param path - A JSONPath query.
* @param value - JSON-like data to which the query _path_ will be applied.
* @returns The first node in _value_ matching _path_, or `undefined` if
* there are no matches.
*/
export function match(
path: string,
value: JSONValue,
): JSONPathNode | undefined {
return DEFAULT_ENVIRONMENT.match(path, value);
}
12 changes: 12 additions & 0 deletions src/path/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ export class JSONPath {
return nodes;
}

/**
* Return a {@link JSONPathNode} instance for the first object found in
* _value_ matching this query.
*
* @param value - JSON-like data to which this query will be applied.
* @returns The first node in _value_ matching this query, or `undefined` if
* there are no matches.
*/
public match(value: JSONValue): JSONPathNode | undefined {
return this.query(value).nodes.at(0);
}

/**
*
*/
Expand Down
2 changes: 0 additions & 2 deletions src/pointer/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ export class JSONPointer {
}
}

// TODO: add `resolveWithFallback` to handle explicit `undefined`

/**
*
* @param value -
Expand Down
13 changes: 12 additions & 1 deletion tests/path/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JSONPathEnvironment } from "../../src/path";
import { JSONPathEnvironment, JSONPathNode } from "../../src/path";

describe("JSONPath API", () => {
const env = new JSONPathEnvironment();
Expand Down Expand Up @@ -65,4 +65,15 @@ describe("JSONPath API", () => {
"/some/bar/0",
]);
});
test("pointer from empty node", () => {
const node = new JSONPathNode("", [], {});
expect(node.toPointer().toString()).toBe("");
});
test("match first available node", () => {
const node = env.match("$.foo", { foo: [1, 2, 3] });
expect(node).toBeInstanceOf(JSONPathNode);
if (node) {
expect(node.value).toStrictEqual([1, 2, 3]);
}
});
});

0 comments on commit 929e49a

Please sign in to comment.