Skip to content

Commit 356762d

Browse files
committed
feat: add pathSegments method
1 parent 2b28c5b commit 356762d

File tree

4 files changed

+91
-10
lines changed

4 files changed

+91
-10
lines changed

src/jsonpath_js.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ import type { JsonpathQuery } from "./grammar/ast";
22
import { parse } from "./grammar/jsonpath_js";
33
import { run } from "./parser";
44
import type { Json } from "./types/json";
5+
import { escapeMemberName } from "./utils/escapeMemberName";
56

67
type PathResult = {
78
value: Json;
89
path: string;
910
};
1011

12+
type PathSegmentsResult = {
13+
value: Json;
14+
segments: (string | number)[];
15+
};
16+
1117
export class JSONPathJS {
1218
rootNode: JsonpathQuery;
1319

@@ -23,14 +29,39 @@ export class JSONPathJS {
2329
.map((json) => json.value);
2430
}
2531

32+
#convertPathSegmentToString(segment: string | number): string {
33+
if (typeof segment === "string") {
34+
if (segment === "$") {
35+
return "$";
36+
}
37+
return `['${escapeMemberName(segment)}']`;
38+
}
39+
return `[${segment}]`;
40+
}
41+
2642
paths(json: Json): PathResult[] {
43+
const resultNodeList = run(json, this.rootNode);
44+
return resultNodeList
45+
.filter((json) => json !== undefined)
46+
.map((json) => {
47+
const normalizedPath = json.path
48+
.map((segment) => this.#convertPathSegmentToString(segment))
49+
.join("");
50+
return {
51+
value: json.value,
52+
path: normalizedPath,
53+
};
54+
});
55+
}
56+
57+
pathSegments(json: Json): PathSegmentsResult[] {
2758
const resultNodeList = run(json, this.rootNode);
2859
return resultNodeList
2960
.filter((json) => json !== undefined)
3061
.map((json) => {
3162
return {
3263
value: json.value,
33-
path: json.path,
64+
segments: json.path,
3465
};
3566
});
3667
}

src/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import type { Json } from "./types/json";
44
import { createNode, type Node, type NodeList } from "./types/node";
55

66
export function run(json: Json, query: JsonpathQuery): NodeList {
7-
const rootNode: Node = createNode(json, "$");
7+
const rootNode: Node = createNode(json, ["$"]);
88
return applyRoot(query, rootNode);
99
}

src/types/node.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { escapeMemberName } from "../utils/escapeMemberName";
21
import type { Json } from "./json";
32

43
const nodeType: unique symbol = Symbol("NodeType");
54

6-
export type Node = { [nodeType]: unknown; value: Json; path: string };
5+
export type PathSegment = string | number;
6+
export type Node = { [nodeType]: unknown; value: Json; path: PathSegment[] };
77
export type NodeList = Node[];
88

9-
export function createNode(json: Json, path: string): Node {
9+
export function createNode(json: Json, path: PathSegment[]): Node {
1010
return { [nodeType]: undefined, value: json, path };
1111
}
1212

@@ -15,14 +15,11 @@ export function addMemberPath(
1515
newValue: Json,
1616
memberName: string,
1717
): Node {
18-
return createNode(
19-
newValue,
20-
`${base.path}['${escapeMemberName(memberName)}']`,
21-
);
18+
return createNode(newValue, [...base.path, memberName]);
2219
}
2320

2421
export function addIndexPath(base: Node, newValue: Json, index: number): Node {
25-
return createNode(newValue, `${base.path}[${index}]`);
22+
return createNode(newValue, [...base.path, index]);
2623
}
2724

2825
export function isNode(node: unknown): node is Node {

tests/path_segments.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { expect, it } from "vitest";
2+
import { JSONPathJS } from "../src";
3+
4+
const book1 = {
5+
category: "reference",
6+
author: "Nigel Rees",
7+
title: "Sayings of the Century",
8+
price: 8.95,
9+
};
10+
11+
const book2 = {
12+
category: "fiction",
13+
author: "Evelyn Waugh",
14+
title: "Sword of Honour",
15+
price: 12.99,
16+
};
17+
18+
const book3 = {
19+
category: "fiction",
20+
author: "Herman Melville",
21+
title: "Moby Dick",
22+
isbn: "0-553-21311-3",
23+
price: 8.99,
24+
};
25+
26+
const book4 = {
27+
category: "fiction",
28+
author: "J. R. R. Tolkien",
29+
title: "The Lord of the Rings",
30+
isbn: "0-395-19395-8",
31+
price: 22.99,
32+
};
33+
34+
const json = {
35+
store: {
36+
book: [book1, book2, book3, book4],
37+
bicycle: {
38+
color: "red",
39+
price: 19.95,
40+
},
41+
},
42+
};
43+
44+
it("test", () => {
45+
const path = new JSONPathJS("$.store.book[*].author");
46+
const pathSegmentsList = path.pathSegments(json).map((path) => path.segments);
47+
48+
expect(pathSegmentsList.length).toBe(4);
49+
expect(pathSegmentsList[0]).toEqual(["$", "store", "book", 0, "author"]);
50+
expect(pathSegmentsList[1]).toEqual(["$", "store", "book", 1, "author"]);
51+
expect(pathSegmentsList[2]).toEqual(["$", "store", "book", 2, "author"]);
52+
expect(pathSegmentsList[3]).toEqual(["$", "store", "book", 3, "author"]);
53+
});

0 commit comments

Comments
 (0)