1
0
silverbullet/common/json.ts
2024-01-08 09:12:54 +01:00

74 lines
2.0 KiB
TypeScript

import { BSON } from "https://esm.sh/bson@6.2.0";
// BSON doesn't support top-level primitives, so we need to wrap them in an object
const topLevelValueKey = "$_tl";
// BSON doesn't support undefined, so we need to encode it as a "magic" string
const undefinedPlaceHolder = "$_undefined_$";
/**
* BSON encoder, but also supporting "edge cases" like encoding strings, numbers, etc.
* @param obj
* @returns
*/
export function encodeBSON(obj: any): Uint8Array {
if (
obj === undefined || obj === null ||
!(typeof obj === "object" && obj.constructor === Object)
) {
obj = { [topLevelValueKey]: obj };
}
obj = traverseAndRewriteJSON(obj, (val) => {
if (val === undefined) {
return undefinedPlaceHolder;
}
return val;
});
return BSON.serialize(obj);
}
export function decodeBSON(data: Uint8Array): any {
let result = BSON.deserialize(data);
// For whatever reason the BSON library doesn't unwrap binary blobs automatically
result = traverseAndRewriteJSON(result, (val) => {
if (typeof val?.value === "function") {
return val.value();
} else if (val === undefinedPlaceHolder) {
return undefined;
}
return val;
});
if (Object.hasOwn(result, topLevelValueKey)) {
return result[topLevelValueKey];
} else {
return result;
}
}
/**
* Traverses and rewrites an object recursively.
*
* @param obj - The object to traverse and rewrite.
* @param rewrite - The function to apply for rewriting each value.
* @returns The rewritten object.
*/
export function traverseAndRewriteJSON(
obj: any,
rewrite: (val: any) => any,
): any {
// Apply rewrite to object as a whole
obj = rewrite(obj);
// Recurse down if this is an array or a "plain object"
if (
obj && (Array.isArray(obj) ||
(typeof obj === "object" && obj.constructor === Object))
) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
obj[key] = traverseAndRewriteJSON(obj[key], rewrite);
}
}
return obj;
}