Skip to content

Commit d5cc8bc

Browse files
committed
Add JSON utility functions
1 parent 6677652 commit d5cc8bc

File tree

4 files changed

+219
-8
lines changed

4 files changed

+219
-8
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hyperjump/browser",
3-
"version": "2.0.0",
3+
"version": "2.0.0-dev",
44
"description": "A client for working with JSON Reference (`$ref`) documents",
55
"keywords": [
66
"$ref",
@@ -19,7 +19,7 @@
1919
"type": "module",
2020
"exports": {
2121
".": "./src/hyperjump/index.js",
22-
"./rejson": "./src/json/index.js",
22+
"./json": "./src/json/index.js",
2323
"./jref": "./src/jref/index.js"
2424
},
2525
"bin": {

src/hyperjump/node-functions.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ describe("JSON Browser", () => {
4242
});
4343

4444
test("true", () => {
45-
expect(node && hyperjump.has("foo", node)).to.eql(true);
45+
expect(hyperjump.has("foo", node)).to.eql(true);
4646
});
4747

4848
test("false", () => {
49-
expect(node && hyperjump.has("bar", node)).to.eql(false);
49+
expect(hyperjump.has("bar", node)).to.eql(false);
5050
});
5151
});
5252

@@ -63,7 +63,7 @@ describe("JSON Browser", () => {
6363
expect(hyperjump.length(subject)).to.eql(1);
6464
});
6565

66-
describe("object has property", () => {
66+
describe("typeOf", () => {
6767
const hyperjump = new Hyperjump();
6868

6969
beforeEach(() => {

src/json/jsonast-util.js

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import { JsonLexer } from "./json-lexer.js";
1313
* JsonObjectNode,
1414
* JsonPropertyNameNode,
1515
* JsonPropertyNode,
16-
* JsonStringNode
16+
* JsonStringNode,
17+
* JsonType
1718
* } from "./jsonast.js"
1819
*/
1920

@@ -336,6 +337,84 @@ const unescapePointerSegment = (segment) => segment.toString().replace(/~1/g, "/
336337
/** @type (segment: string) => string */
337338
const escapePointerSegment = (segment) => segment.toString().replace(/~/g, "~0").replace(/\//g, "~1");
338339

340+
/** @type (node: JsonNode) => unknown */
341+
export const value = (node) => {
342+
switch (node.jsonType) {
343+
case "object":
344+
case "array":
345+
// TODO: Handle structured values
346+
throw Error("Can't get the value of a structured value.");
347+
default:
348+
return node.value;
349+
}
350+
};
351+
352+
/** @type (node: JsonNode) => JsonType */
353+
export const typeOf = (node) => {
354+
return node.jsonType;
355+
};
356+
357+
/** @type (key: string, node: JsonNode) => boolean */
358+
export const has = (key, node) => {
359+
if (node.jsonType === "object") {
360+
for (const property of node.children) {
361+
if (property.children[0].value === key) {
362+
return true;
363+
}
364+
}
365+
}
366+
367+
return false;
368+
};
369+
370+
/** @type (node: JsonNode) => number */
371+
export const length = (node) => {
372+
switch (node.jsonType) {
373+
case "array":
374+
return node.children.length;
375+
case "string":
376+
return node.value.length;
377+
default:
378+
throw Error("Can't get the length of a value that is not an array or a string.");
379+
}
380+
};
381+
382+
/** @type (node: JsonNode) => Generator<JsonNode, void, unknown> */
383+
export const iter = function* (node) {
384+
if (node.jsonType === "array") {
385+
for (const itemNode of node.children) {
386+
yield itemNode;
387+
}
388+
}
389+
};
390+
391+
/** @type (node: JsonNode) => Generator<string, undefined, string> */
392+
export const keys = function* (node) {
393+
if (node.jsonType === "object") {
394+
for (const propertyNode of node.children) {
395+
yield propertyNode.children[0].value;
396+
}
397+
}
398+
};
399+
400+
/** @type (node: JsonNode) => Generator<JsonNode, void, unknown> */
401+
export const values = function* (node) {
402+
if (node.jsonType === "object") {
403+
for (const propertyNode of node.children) {
404+
yield propertyNode.children[1];
405+
}
406+
}
407+
};
408+
409+
/** @type (node: JsonNode) => Generator<[string, JsonNode], void, unknown> */
410+
export const entries = function* (node) {
411+
if (node.jsonType === "object") {
412+
for (const propertyNode of node.children) {
413+
yield [propertyNode.children[0].value, propertyNode.children[1]];
414+
}
415+
}
416+
};
417+
339418
export class JsonPointerError extends Error {
340419
/**
341420
* @param {string} [message]

src/json/jsonast-util.test.js

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { readdir, readFile } from "node:fs/promises";
22
import { resolve } from "node:path";
3-
import { describe, test, expect } from "vitest";
3+
import { describe, test, expect, beforeEach } from "vitest";
44
import { VFileMessage } from "vfile-message";
5-
import { rejson, rejsonStringify } from "./index.js";
5+
import { fromJson, toJson, rejson, rejsonStringify } from "./index.js";
6+
import * as Json from "./index.js";
7+
8+
/**
9+
* @import { JsonNode } from "./index.js"
10+
*/
611

712

813
describe("jsonast-util", async () => {
@@ -36,4 +41,131 @@ describe("jsonast-util", async () => {
3641
});
3742
}
3843
}
44+
45+
describe("object has property", () => {
46+
/** @type JsonNode */
47+
let subject;
48+
49+
beforeEach(() => {
50+
subject = fromJson(`{
51+
"foo": 42
52+
}`);
53+
});
54+
55+
test("true", () => {
56+
expect(Json.has("foo", subject)).to.eql(true);
57+
});
58+
59+
test("false", () => {
60+
expect(Json.has("bar", subject)).to.eql(false);
61+
});
62+
});
63+
64+
test("length of array", () => {
65+
const subject = fromJson(`[42]`);
66+
67+
expect(Json.length(subject)).to.eql(1);
68+
});
69+
70+
test("length of string", () => {
71+
const subject = fromJson(`"foo"`);
72+
73+
expect(Json.length(subject)).to.eql(3);
74+
});
75+
76+
describe("typeOf", () => {
77+
test("null", () => {
78+
const subject = fromJson(`null`);
79+
expect(Json.typeOf(subject)).to.eql("null");
80+
});
81+
82+
test("true", () => {
83+
const subject = fromJson(`true`);
84+
expect(Json.typeOf(subject)).to.eql("boolean");
85+
});
86+
87+
test("false", () => {
88+
const subject = fromJson(`false`);
89+
expect(Json.typeOf(subject)).to.eql("boolean");
90+
});
91+
92+
test("number", () => {
93+
const subject = fromJson(`42`);
94+
expect(Json.typeOf(subject)).to.eql("number");
95+
});
96+
97+
test("string", () => {
98+
const subject = fromJson(`"foo"`);
99+
expect(Json.typeOf(subject)).to.eql("string");
100+
});
101+
102+
test("array", () => {
103+
const subject = fromJson(`["foo", 42]`);
104+
expect(Json.typeOf(subject)).to.eql("array");
105+
});
106+
107+
test("object", () => {
108+
const subject = fromJson(`{ "foo": 42 }`);
109+
expect(Json.typeOf(subject)).to.eql("object");
110+
});
111+
});
112+
113+
test("iter", () => {
114+
const subject = fromJson(`[1, 2]`);
115+
116+
const generator = Json.iter(subject);
117+
118+
const first = generator.next();
119+
expect(toJson(first.value)).to.equal(`1`);
120+
const second = generator.next();
121+
expect(toJson(second.value)).to.equal(`2`);
122+
expect((generator.next()).done).to.equal(true);
123+
});
124+
125+
test("keys", () => {
126+
const subject = fromJson(`{
127+
"a": 1,
128+
"b": 2
129+
}`);
130+
131+
const generator = Json.keys(subject);
132+
133+
expect(generator.next().value).to.equal("a");
134+
expect(generator.next().value).to.equal("b");
135+
expect(generator.next().done).to.equal(true);
136+
});
137+
138+
test("values", () => {
139+
const subject = fromJson(`{
140+
"a": 1,
141+
"b": 2
142+
}`);
143+
144+
const generator = Json.values(subject);
145+
146+
const first = generator.next();
147+
expect(toJson(first.value)).to.equal(`1`);
148+
const second = generator.next();
149+
expect(toJson(second.value)).to.equal(`2`);
150+
expect((generator.next()).done).to.equal(true);
151+
});
152+
153+
test("entries", () => {
154+
const subject = fromJson(`{
155+
"a": 1,
156+
"b": 2
157+
}`);
158+
159+
const generator = Json.entries(subject);
160+
161+
const a = /** @type [string, JsonNode] */ ((generator.next()).value);
162+
expect(a[0]).to.equal("a");
163+
expect(toJson(a[1])).to.equal(`1`);
164+
165+
const b = /** @type [string, JsonNode] */ ((generator.next()).value);
166+
expect(b[0]).to.equal("b");
167+
expect(toJson(b[1])).to.equal(`2`);
168+
169+
expect(generator.next().done).to.equal(true);
170+
});
39171
});

0 commit comments

Comments
 (0)