fix(Smartjson): Cross-platform buffer/base64 handling, safer folding with cycle detection, parsing fixes, docs and dependency updates

This commit is contained in:
2025-09-12 19:16:52 +00:00
parent 9bb6e0b497
commit 2f012fc0ad
9 changed files with 5547 additions and 3223 deletions

View File

@@ -1,8 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@push.rocks/smartjson',
version: '5.0.20',
version: '5.0.21',
description: 'A library for handling typed JSON data, providing functionalities for parsing, stringifying, and working with JSON objects, including support for encoding and decoding buffers.'
}

View File

@@ -16,10 +16,23 @@ type TParseReplacer = (this: any, key: string, value: any) => any;
// Utility functions to handle base64 encoding/decoding in a cross-platform way
function base64Encode(data: Uint8Array): string {
// Prefer Node's Buffer when available
if (typeof Buffer !== 'undefined') {
// @ts-ignore Buffer might not exist in browser builds
return Buffer.from(data).toString('base64');
}
// Fallback for browsers
return btoa(String.fromCharCode(...data));
}
function base64Decode(str: string): Uint8Array {
// Prefer Node's Buffer when available
if (typeof Buffer !== 'undefined') {
// @ts-ignore Buffer might not exist in browser builds
const buf = Buffer.from(str, 'base64');
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
// Fallback for browsers
return new Uint8Array(Array.from(atob(str)).map((char) => char.charCodeAt(0)));
}

View File

@@ -11,10 +11,11 @@ interface JsonObject {
export const parse = bufferhandling.parse;
export const parseJsonL = (jsonlData: string): JsonObject[] => {
const lines = jsonlData.trim().split('\n');
const lines = jsonlData.split('\n');
const parsedData: JsonObject[] = lines.reduce((acc, line) => {
if (line.trim().length > 0) {
acc.push(JSON.parse(line));
const trimmed = line.trim();
if (trimmed.length > 0) {
acc.push(parse(trimmed));
}
return acc;
}, [] as JsonObject[]);
@@ -49,18 +50,18 @@ export const stringifyBase64 = (...args: Parameters<typeof stringify>): string =
};
export const parseBase64 = (base64JsonStringArg: string) => {
const simpleStringified = plugins.smartstring.base64.decode(base64JsonStringArg);
const base64 = plugins.smartstring.base64 as any;
const decodeFn: (input: string) => string = base64.decodeUri || base64.decode;
const simpleStringified = decodeFn(base64JsonStringArg);
return parse(simpleStringified);
};
parse;
export class Smartjson {
/**
* enfolds data from an object
*/
public static enfoldFromObject(objectArg) {
const newInstance = new this();
public static enfoldFromObject<T extends typeof Smartjson>(this: T, objectArg: any): InstanceType<T> {
const newInstance = new this() as InstanceType<T>;
for (const keyName in objectArg) {
if (newInstance.saveableProperties.indexOf(keyName) !== -1) {
newInstance[keyName] = objectArg[keyName];
@@ -72,7 +73,7 @@ export class Smartjson {
/**
* enfold from json
*/
public static enfoldFromJson(jsonArg: string) {
public static enfoldFromJson<T extends typeof Smartjson>(this: T, jsonArg: string): InstanceType<T> {
const objectFromJson = parse(jsonArg);
return this.enfoldFromObject(objectFromJson);
}
@@ -88,21 +89,49 @@ export class Smartjson {
*/
public foldToObject() {
const newFoldedObject: { [key: string]: any } = {};
const trackMap = [];
for (const keyName of this.saveableProperties) {
let value = this[keyName];
if (value instanceof Smartjson) {
if (trackMap.includes(value)) {
const trackSet = new Set<Smartjson>();
const foldValue = (val: any): any => {
if (val instanceof Smartjson) {
if (trackSet.has(val)) {
throw new Error('cycle detected');
}
trackMap.push(value);
value = value.foldToObject();
trackSet.add(val);
return val.foldToObjectInternal(trackSet);
}
newFoldedObject[keyName] = plugins.lodashCloneDeep(value);
if (Array.isArray(val)) {
return val.map((item) => foldValue(item));
}
return plugins.lodashCloneDeep(val);
};
for (const keyName of this.saveableProperties) {
const value = this[keyName];
newFoldedObject[keyName] = foldValue(value);
}
return newFoldedObject;
}
private foldToObjectInternal(trackSet: Set<Smartjson>) {
const result: { [key: string]: any } = {};
const foldValue = (val: any): any => {
if (val instanceof Smartjson) {
if (trackSet.has(val)) {
throw new Error('cycle detected');
}
trackSet.add(val);
return val.foldToObjectInternal(trackSet);
}
if (Array.isArray(val)) {
return val.map((item) => foldValue(item));
}
return plugins.lodashCloneDeep(val);
};
for (const keyName of this.saveableProperties) {
const value = this[keyName];
result[keyName] = foldValue(value);
}
return result;
}
/**
* folds a class into an object
*/
@@ -131,17 +160,7 @@ export const deepEqualObjects = (object1: any, object2: any): boolean => {
};
export const deepEqualJsonLStrings = (jsonLString1: string, jsonLString2: string): boolean => {
const firstArray = [];
jsonLString1.split('\n').forEach((line) => {
if (line) {
firstArray.push(parse(line));
}
});
const secondArray = [];
jsonLString2.split('\n').forEach((line) => {
if (line) {
secondArray.push(parse(line));
}
});
const firstArray = parseJsonL(jsonLString1);
const secondArray = parseJsonL(jsonLString2);
return deepEqualObjects(firstArray, secondArray);
}