temp unfinished json stuff in ts

This commit is contained in:
Rory&
2026-02-01 03:15:29 +01:00
parent 14e0ad21e2
commit 210e7be279
7 changed files with 377 additions and 1 deletions

View File

@@ -24,7 +24,7 @@
"generate:openapi": "node scripts/openapi.js",
"add:license": "node scripts/license.js",
"migrate-from-staging": "node -r dotenv/config -r module-alias/register scripts/stagingMigration/index.js",
"node:tests": "npm run build:src && node -r dotenv/config -r module-alias/register --enable-source-maps --test --experimental-test-coverage dist/**/*.test.js"
"node:tests": "npm run build:src:tsgo && node -r dotenv/config -r module-alias/register --enable-source-maps --test --experimental-test-coverage dist/**/*.test.js"
},
"main": "dist/bundle/index.js",
"types": "src/bundle/index.ts",

View File

@@ -0,0 +1,31 @@
/// <summary>Defines the various JSON tokens that make up a JSON text.</summary>
export enum JsonTokenType {
/// <summary>There is no value (as distinct from <see cref="F:System.Text.Json.JsonTokenType.Null" />). This is the default token type if no data has been read by the <see cref="T:System.Text.Json.Utf8JsonReader" />.</summary>
None,
/// <summary>The token type is the start of a JSON object.</summary>
StartObject,
/// <summary>The token type is the end of a JSON object.</summary>
EndObject,
/// <summary>The token type is the start of a JSON array.</summary>
StartArray,
/// <summary>The token type is the end of a JSON array.</summary>
EndArray,
/// <summary>The token type is a JSON property name.</summary>
PropertyName,
/// <summary>The token type is a comment string.</summary>
Comment,
/// <summary>The token type is a JSON string.</summary>
String,
/// <summary>The token type is a JSON number.</summary>
Number,
/// <summary>The token type is the JSON literal true.</summary>
True,
/// <summary>The token type is the JSON literal false.</summary>
False,
/// <summary>The token type is the JSON literal null.</summary>
Null,
}
export class JsonNode {
type: JsonTokenType = JsonTokenType.None;
}

View File

@@ -0,0 +1,158 @@
import { JsonSerializer } from "./JsonSerializer";
import { describe, it } from "node:test";
import { strict as assert } from "assert";
import fs from "fs/promises";
import { Stopwatch } from "../Stopwatch";
import { JsonValue } from "@protobuf-ts/runtime";
describe("JsonSerializer", () => {
it("should serialize synchronously", () => {
const obj = { a: 1, b: "test" };
const result = JsonSerializer.Serialize(obj);
assert.equal(result, '{"a":1,"b":"test"}');
});
it("should deserialize synchronously", () => {
const json = '{"a":1,"b":"test"}';
const result = JsonSerializer.Deserialize(json);
assert.deepEqual(result, { a: 1, b: "test" });
});
it("should serialize asynchronously", async () => {
const obj = { a: 1, b: "test" };
const result = await JsonSerializer.SerializeAsync(obj);
assert.equal(result, '{"a":1,"b":"test"}');
});
it("should deserialize asynchronously", async () => {
const json = '{"a":1,"b":"test"}';
const result = await JsonSerializer.DeserializeAsync(json);
assert.deepEqual(result, { a: 1, b: "test" });
});
it("should be able to read large file", async () => {
// write a massive json file
const sw = Stopwatch.startNew();
const jsonfile = await fs.open("large.json", "w");
await jsonfile.write("[");
const getLargeObj = (index: number, depth: number) => {
const obj: JsonValue = {};
if (depth === 0) {
return obj;
}
for (let i = 0; i < 10; i++) {
obj[`key${i}`] = getLargeObj(index * 10 + i, depth - 1);
}
return obj;
};
for (let i = 0; i < 100; i++) {
const entry = JSON.stringify(getLargeObj(i, 5));
await jsonfile.write(entry);
if (i < 99) {
await jsonfile.write(",");
}
}
await jsonfile.write("]");
await jsonfile.close();
process.stdout.write("Large file written in " + sw.elapsed().toString() + "\n");
const jsonData = await fs.readFile("large.json", "utf-8");
const start = process.hrtime.bigint();
const obj = await JsonSerializer.DeserializeAsync<{ key: string; value: string }[]>(jsonData);
const end = process.hrtime.bigint();
const duration = end - start;
console.log(`Deserialization took ${duration / BigInt(1e6)} ms`);
assert.equal(obj.length, 100);
await fs.unlink("large.json");
});
it("should be able to parallelise", async () => {
// write a massive json file
const sw = Stopwatch.startNew();
const jsonfile = await fs.open("large.json", "w");
await jsonfile.write("[");
const getLargeObj = (index: number, depth: number) => {
const obj: JsonValue = {};
if (depth === 0) {
return obj;
}
for (let i = 0; i < 5; i++) {
obj[`key${i}`] = getLargeObj(index * 10 + i, depth - 1);
}
return obj;
};
for (let i = 0; i < 50; i++) {
const entry = JSON.stringify(getLargeObj(i, 5));
await jsonfile.write(entry);
if (i < 49) {
await jsonfile.write(",");
}
}
await jsonfile.write("]");
await jsonfile.close();
process.stdout.write("Large file written in " + sw.elapsed().toString() + "\n");
const tasks = [];
const start = process.hrtime.bigint();
for (let i = 0; i < 64; i++) {
tasks.push(
(async () => {
const jsonData = await fs.readFile("large.json", "utf-8");
const obj = await JsonSerializer.DeserializeAsync<{ key: string; value: string }[]>(jsonData);
const end = process.hrtime.bigint();
const duration = end - start;
console.log(`Deserialization took ${duration / BigInt(1e6)} ms`);
assert.equal(obj.length, 50);
})(),
);
}
await Promise.all(tasks);
await fs.unlink("large.json");
});
// TODO: broken
// it("should be able to stream large file", async () => {
// // write a massive json file
// const sw = Stopwatch.startNew();
// const jsonfile = await fs.open("large.json", "w");
// await jsonfile.write("[");
// const getLargeObj = (index: number, depth: number) => {
// const obj: JsonValue = {};
//
// if (depth === 0) {
// return obj;
// }
// for (let i = 0; i < 10; i++) {
// obj[`key${i}`] = getLargeObj(index * 10 + i, depth - 1);
// }
// return obj;
// };
// for (let i = 0; i < 100; i++) {
// const entry = JSON.stringify(getLargeObj(i, 5));
// await jsonfile.write(entry);
// if (i < 99) {
// await jsonfile.write(",");
// }
// }
// await jsonfile.write("]");
// await jsonfile.close();
// process.stdout.write("Large file written in " + sw.elapsed().toString() + "\n");
//
// const jsonData = await fs.open("large.json", "r").then((f) => f.createReadStream());
//
// const start = process.hrtime.bigint();
// const obj = await JsonSerializer.DeserializeAsync<{ key: string; value: string }[]>(jsonData);
// const end = process.hrtime.bigint();
// const duration = end - start;
// console.log(`Deserialization took ${duration / BigInt(1e6)} ms`);
//
// assert.equal(obj.length, 100);
// await fs.unlink("large.json");
// });
});

View File

@@ -0,0 +1,166 @@
import { JsonSerializerOptions } from "./JsonSerializerOptions";
import { Worker } from "worker_threads";
import { join } from "path";
import os from "os";
import Stream from "node:stream";
import { ReadStream, WriteStream } from "node:fs";
// const worker = new Worker(join(process.cwd(), 'dist', 'util', 'util', 'json', 'jsonWorker.js'));
const workerPool: Worker[] = [];
const numWorkers = process.env.JSON_WORKERS ? parseInt(process.env.JSON_WORKERS) : os.cpus().length;
for (let i = 0; i < numWorkers; i++) {
console.log("[JsonSerializer] Starting JSON worker", i);
workerPool.push(new Worker(join(__dirname, "jsonWorker.js")));
workerPool[i].unref();
workerPool[i].setMaxListeners(64);
}
let currentWorkerIndex = 0;
function getNextWorker(): Worker {
const worker = workerPool[currentWorkerIndex];
currentWorkerIndex = (currentWorkerIndex + 1) % numWorkers;
return worker;
}
export class JsonSerializer {
public static Serialize<T>(value: T, opts?: JsonSerializerOptions): string {
return JSON.stringify(value);
}
public static async SerializeAsync<T>(value: T, opts?: JsonSerializerOptions): Promise<string> {
const worker = getNextWorker();
worker.postMessage({ type: "serialize", value });
return new Promise((resolve, reject) => {
const handler = (msg: { result?: string; error?: string }) => {
clearTimeout(timeout);
worker.removeListener("message", handler);
if (msg.error) {
reject(new Error(msg.error));
} else {
resolve(msg.result!);
}
};
worker.on("message", handler);
const timeout = setTimeout(() => {
worker.removeListener("message", handler);
reject(new Error("Worker timeout"));
}, 60000);
});
}
public static Deserialize<T>(json: string, opts?: JsonSerializerOptions): T {
return JSON.parse(json) as T;
}
public static async DeserializeAsync<T>(json: string | ReadableStream | ReadStream, opts?: JsonSerializerOptions): Promise<T> {
if (json instanceof ReadableStream) return this.DeserializeAsyncReadableStream<T>(json, opts);
if (json instanceof ReadStream) return this.DeserializeAsyncReadStream<T>(json, opts);
const worker = getNextWorker();
worker.postMessage({ type: "deserialize", json });
return new Promise((resolve, reject) => {
const handler = (msg: { result?: string; error?: string }) => {
clearTimeout(timeout);
worker.removeListener("message", handler);
if (msg.error) {
reject(new Error(msg.error));
} else {
resolve(JSON.parse(msg.result!) as T);
}
};
worker.on("message", handler);
const timeout = setTimeout(() => {
worker.removeListener("message", handler);
reject(new Error("Worker timeout"));
}, 60000);
});
}
private static async DeserializeAsyncReadableStream<T>(jsonStream: ReadableStream, opts?: JsonSerializerOptions): Promise<T> {
const reader = jsonStream.getReader();
let jsonData = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
jsonData += new TextDecoder().decode(value);
}
return this.DeserializeAsync<T>(jsonData, opts);
}
private static async DeserializeAsyncReadStream<T>(jsonStream: ReadStream, opts?: JsonSerializerOptions): Promise<T> {
let jsonData = "";
for await (const chunk of jsonStream) {
jsonData += chunk.toString();
}
return this.DeserializeAsync<T>(jsonData, opts);
}
public static async *DeserializeAsyncEnumerable<T>(json: string | ReadStream | ReadableStream, opts?: JsonSerializerOptions): AsyncGenerator<T, void, unknown> {
if (json instanceof ReadableStream) return yield* this.DeserializeAsyncEnumerableReadableStream<T>(json, opts);
if (json instanceof ReadStream) return yield* this.DeserializeAsyncEnumerableReadStream<T>(json, opts);
const arr = await this.DeserializeAsync<T[]>(json, opts);
for (const item of arr) {
yield item;
}
}
private static async *DeserializeAsyncEnumerableReadableStream<T>(json: ReadableStream, opts?: JsonSerializerOptions) {
const reader = json.getReader();
//TODO: implement
yield undefined as unknown as T;
}
private static async *DeserializeAsyncEnumerableReadStream<T>(json: ReadStream, opts?: JsonSerializerOptions) {
// TODO: implement
yield undefined as unknown as T;
}
public static async SerializeAsyncEnumerableToStringAsync<T>(items: AsyncIterable<T>, opts?: JsonSerializerOptions): Promise<string> {
let jsonData = "[";
let first = true;
for await (const item of items) {
if (!first) {
jsonData += ",";
} else {
first = false;
}
jsonData += await this.SerializeAsync(item, opts);
}
jsonData += "]";
return jsonData;
}
public static async SerializeAsyncEnumerableAsync<T>(items: AsyncIterable<T>, stream: WriteStream | WritableStream, opts?: JsonSerializerOptions): Promise<void> {}
private static async SerializeAsyncEnumerableToWritableStreamAsync<T>(items: AsyncIterable<T>, stream: WritableStream, opts?: JsonSerializerOptions): Promise<void> {
const writer = stream.getWriter();
let first = true;
await writer.write(new TextEncoder().encode("["));
for await (const item of items) {
if (!first) {
await writer.write(new TextEncoder().encode(","));
} else {
first = false;
}
const jsonItem = await this.SerializeAsync(item, opts);
await writer.write(new TextEncoder().encode(jsonItem));
}
await writer.write(new TextEncoder().encode("]"));
await writer.close();
}
private static async SerializeAsyncEnumerableToWriteStreamAsync<T>(items: AsyncIterable<T>, stream: WriteStream, opts?: JsonSerializerOptions): Promise<void> {
let first = true;
stream.write("[");
for await (const item of items) {
if (!first) {
stream.write(",");
} else {
first = false;
}
const jsonItem = await this.SerializeAsync(item, opts);
stream.write(jsonItem);
}
stream.write("]");
stream.end();
}
}

View File

@@ -0,0 +1 @@
export class JsonSerializerOptions {}

View File

@@ -0,0 +1,4 @@
JSON utils
=============
JSON library in typescript, based around Dotnet's System.Text.Json api.

View File

@@ -0,0 +1,16 @@
import { parentPort } from "worker_threads";
parentPort?.on("message", (message) => {
try {
if (message.type === "serialize") {
const result = JSON.stringify(message.value);
parentPort?.postMessage({ result });
} else if (message.type === "deserialize") {
const parsed = JSON.parse(message.json);
const result = JSON.stringify(parsed);
parentPort?.postMessage({ result });
}
} catch (error) {
parentPort?.postMessage({ error: (error as Error).message });
}
});