Skip to content

Commit 8545a1e

Browse files
committed
Add JSON utility functions
1 parent 6677652 commit 8545a1e

File tree

4 files changed

+195
-8
lines changed

4 files changed

+195
-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: 68 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,72 @@ 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 jsonValue = (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 jsonTypeOf = (node) => {
354+
return node.jsonType;
355+
};
356+
357+
/** @type (key: string, node: JsonNode) => boolean */
358+
export const jsonObjectHas = (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) => Generator<JsonNode, void, unknown> */
371+
export const jsonArrayIter = function* (node) {
372+
if (node.jsonType === "array") {
373+
for (const itemNode of node.children) {
374+
yield itemNode;
375+
}
376+
}
377+
};
378+
379+
/** @type (node: JsonNode) => Generator<string, undefined, string> */
380+
export const jsonObjectKeys = function* (node) {
381+
if (node.jsonType === "object") {
382+
for (const propertyNode of node.children) {
383+
yield propertyNode.children[0].value;
384+
}
385+
}
386+
};
387+
388+
/** @type (node: JsonNode) => Generator<JsonNode, void, unknown> */
389+
export const jsonObjectValues = function* (node) {
390+
if (node.jsonType === "object") {
391+
for (const propertyNode of node.children) {
392+
yield propertyNode.children[1];
393+
}
394+
}
395+
};
396+
397+
/** @type (node: JsonNode) => Generator<[string, JsonNode], void, unknown> */
398+
export const jsonObjectEntries = function* (node) {
399+
if (node.jsonType === "object") {
400+
for (const propertyNode of node.children) {
401+
yield [propertyNode.children[0].value, propertyNode.children[1]];
402+
}
403+
}
404+
};
405+
339406
export class JsonPointerError extends Error {
340407
/**
341408
* @param {string} [message]

src/json/jsonast-util.test.js

Lines changed: 122 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,119 @@ 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.jsonObjectHas("foo", subject)).to.eql(true);
57+
});
58+
59+
test("false", () => {
60+
expect(Json.jsonObjectHas("bar", subject)).to.eql(false);
61+
});
62+
});
63+
64+
describe("typeOf", () => {
65+
test("null", () => {
66+
const subject = fromJson(`null`);
67+
expect(Json.jsonTypeOf(subject)).to.eql("null");
68+
});
69+
70+
test("true", () => {
71+
const subject = fromJson(`true`);
72+
expect(Json.jsonTypeOf(subject)).to.eql("boolean");
73+
});
74+
75+
test("false", () => {
76+
const subject = fromJson(`false`);
77+
expect(Json.jsonTypeOf(subject)).to.eql("boolean");
78+
});
79+
80+
test("number", () => {
81+
const subject = fromJson(`42`);
82+
expect(Json.jsonTypeOf(subject)).to.eql("number");
83+
});
84+
85+
test("string", () => {
86+
const subject = fromJson(`"foo"`);
87+
expect(Json.jsonTypeOf(subject)).to.eql("string");
88+
});
89+
90+
test("array", () => {
91+
const subject = fromJson(`["foo", 42]`);
92+
expect(Json.jsonTypeOf(subject)).to.eql("array");
93+
});
94+
95+
test("object", () => {
96+
const subject = fromJson(`{ "foo": 42 }`);
97+
expect(Json.jsonTypeOf(subject)).to.eql("object");
98+
});
99+
});
100+
101+
test("iter", () => {
102+
const subject = fromJson(`[1, 2]`);
103+
104+
const generator = Json.jsonArrayIter(subject);
105+
106+
const first = generator.next();
107+
expect(toJson(first.value)).to.equal(`1`);
108+
const second = generator.next();
109+
expect(toJson(second.value)).to.equal(`2`);
110+
expect((generator.next()).done).to.equal(true);
111+
});
112+
113+
test("keys", () => {
114+
const subject = fromJson(`{
115+
"a": 1,
116+
"b": 2
117+
}`);
118+
119+
const generator = Json.jsonObjectKeys(subject);
120+
121+
expect(generator.next().value).to.equal("a");
122+
expect(generator.next().value).to.equal("b");
123+
expect(generator.next().done).to.equal(true);
124+
});
125+
126+
test("values", () => {
127+
const subject = fromJson(`{
128+
"a": 1,
129+
"b": 2
130+
}`);
131+
132+
const generator = Json.jsonObjectValues(subject);
133+
134+
const first = generator.next();
135+
expect(toJson(first.value)).to.equal(`1`);
136+
const second = generator.next();
137+
expect(toJson(second.value)).to.equal(`2`);
138+
expect((generator.next()).done).to.equal(true);
139+
});
140+
141+
test("entries", () => {
142+
const subject = fromJson(`{
143+
"a": 1,
144+
"b": 2
145+
}`);
146+
147+
const generator = Json.jsonObjectEntries(subject);
148+
149+
const a = /** @type [string, JsonNode] */ ((generator.next()).value);
150+
expect(a[0]).to.equal("a");
151+
expect(toJson(a[1])).to.equal(`1`);
152+
153+
const b = /** @type [string, JsonNode] */ ((generator.next()).value);
154+
expect(b[0]).to.equal("b");
155+
expect(toJson(b[1])).to.equal(`2`);
156+
157+
expect(generator.next().done).to.equal(true);
158+
});
39159
});

0 commit comments

Comments
 (0)