Skip to content

feat: add simple replace method #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
183 changes: 183 additions & 0 deletions lib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { JsonStreamStringify } from 'json-stream-stringify';
import { console } from 'node:inspector/promises';
import { Writable} from "node:stream";

const MAX_SIZE = 10000;

type JSONValue = string | number | boolean | null | Record<string, JSONValue> | JSONValue[];

export function nativeStringify(data: object, maxSize = MAX_SIZE) {
return JSON.stringify(data).slice(0, maxSize)
}
@@ -222,3 +228,180 @@ export function stringifyWithLimit(value: object, maxSize = MAX_SIZE) {
return output;
}

function streamerReplacer(key: string, value: any) {
if (typeof value === 'string') {
return value.slice(0, 256) + '...truncated..."';
}
return value
}


// async function* outWrite(source: any) {
// console.log(source)
// }

// class MyWriter extends Writable {
// remaining = 0;
// bufferx = "";

// write(chunk: unknown, encoding?: unknown, callback?: unknown): boolean {
// if (this.remaining < 0) {
// // console.log(this.buffer)
// console.log('done')
// // no write
// return true
// } else {
// let size = (chunk as any).length;
// this.remaining -= size;

// this.bufferx += chunk as string;

// return true
// }
// }
// }

// export async function stringifyWithStreamer(value: any, maxSize = MAX_SIZE) {
// // let writer = JSONStream.stringify();

// // writer.write("hallo")
// // writer.write("hallo")
// // writer.write("hallo")
// // writer.write("hallo")
// // writer.write("hallo")

// // console.log(writer.flush())
// const outWrite = new MyWriter();
// outWrite.remaining = 1000;
// outWrite.on('pipe', (src) => {
// console.log('Something is piping into the writer.');
// });

// const going = new JsonStreamStringify(value, streamerReplacer)
// going.on('data', (x) => {
// // console.log(x)

// })
// // going.pipe(process.stdout);
// // going.pipe(outWrite)

// // console.log(outWrite)
// let out: string = await new Promise(resolve => {
// outWrite.on('finish', () => {
// resolve(outWrite.bufferx)
// })
// going.pipe(outWrite)
// })

// console.log(out)

// return out
// }

function scoreType(value: any): number {
if (typeof value === 'number') {
return 8;
} else if (typeof value ==='string') {
return 2 + value.length;
} else if (typeof value ==='boolean') {
return 4;
} else if (value === null) {
return 4;
} else if (typeof value === 'undefined') {
return 0;
}
return NaN;
}

type Context = {
remaining: number;
}

const EMPTY_RECORD = Symbol('empty-record');
const EMPTY_VALUE = Symbol('empty-value');
type Accumulator = Record<string, JSONValue> | typeof EMPTY_RECORD | typeof EMPTY_VALUE

function emptyTokenReplacer(_key: string, value: unknown) {
if (Array.isArray(value)) {
let emptyRecordsCount = value.reduce((acc, item) => acc += item === EMPTY_RECORD, 0)
let emptyValueCount = value.reduce((acc, item) => acc += item === EMPTY_VALUE, 0)
value = value.filter(item => item !== EMPTY_RECORD && item !== EMPTY_VALUE);
if (emptyRecordsCount > 0 && Array.isArray(value)) {
value.push({__truncatedRecords: emptyRecordsCount});
}
if (emptyValueCount > 0 && Array.isArray(value)) {
value.push({__truncatedItems: emptyValueCount});
}

return value
}

return value
}

function _stringifyUpdateObject(value: JSONValue, context: Context): JSONValue {
if (Array.isArray(value)) {
throw new Error("ARRAY!>!")
}
if (typeof value === 'object' && value !== null) {
let out = Object.entries(value).reduce((acc, [key, val]) => {
if (Array.isArray(val)) {
val = val.map((x) => _stringifyUpdateObject(x, context))
} else if (typeof val === 'object') {
val = _stringifyUpdateObject(val, context)
} else {
if (context.remaining < 0) {
return acc
}
let score = scoreType(key) + scoreType(val);
// do we want to truncate large strings still?
if (score) {
context.remaining -= score;
if (context.remaining < 0) {
if (typeof acc === 'object') {
acc["__truncated"] = true
return acc
} else {
return acc
}
}
}
}
if (acc === EMPTY_RECORD || acc === EMPTY_VALUE) {
acc = {}
}
acc[key] = val;
return acc;
}, EMPTY_RECORD as Accumulator);
return out
}

if (context.remaining < 0) {
return EMPTY_VALUE
}
let score = scoreType(value);
if (score) {
context.remaining -= score;
let OVERFLOW_SLACK = 100
if(context.remaining < OVERFLOW_SLACK && typeof value === 'string') {
value = value.slice(0, -context.remaining) + '...truncated..."';
}
}

return value
}

export function stringifyUpdateObject(value: JSONValue, maxSize = MAX_SIZE) {
let context = {remaining: maxSize}
let out;
// console.log(JSON.stringify(value).length)
if (Array.isArray(value)) {
out = value.map((x) => _stringifyUpdateObject(x, context))
} else {
out = _stringifyUpdateObject(value, context)
}

// console.log(out)
// console.log(JSON.stringify(out))
return JSON.stringify(out, emptyTokenReplacer)
}
6 changes: 4 additions & 2 deletions matrix.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { nativeStringify, truncateStringify, stringifyWithLimit, nativeStringifyClosed } from "./lib";
import { nativeStringify, truncateStringify, stringifyWithLimit, nativeStringifyClosed, stringifyUpdateObject } from "./lib";

// Number of iterations per test
const ITERATIONS = 300;
@@ -31,7 +31,8 @@ const functionList = [
{ name: "JSON.stringify", fn: nativeStringify },
{ name: "JSON.stringify closed", fn: nativeStringifyClosed },
{ name: "truncateStringify", fn: truncateStringify },
{ name: "stringifyWithLimit", fn: stringifyWithLimit }
{ name: "stringifyWithLimit", fn: stringifyWithLimit },
{ name: "stringifyUpdateObject", fn: stringifyUpdateObject },
];

// List of files to benchmark (add as many as needed)
@@ -41,6 +42,7 @@ const fileNames = [
"small-collection.json",
"single-digit.json",
"small-string.json",
"strings-array.json",
];

// Object to store results in a matrix form (file -> function -> formatted average runtime)
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -7,5 +7,8 @@
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"json-stream-stringify": "^3.1.6"
}
}
1,002 changes: 1,002 additions & 0 deletions strings-array.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions thingy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { stringifyUpdateObject } from "./lib";
import xd from "./many-small-strings.json"
import xp from "./small-collection.json"
import wowowwo from "./strings-array.json"

console.log(stringifyUpdateObject(wowowwo))