feat: add JSON escape utility with comprehensive edge case tests
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { safeStringify, isValidJson } from '../escape.js';
|
||||||
|
|
||||||
|
describe('safeStringify', () => {
|
||||||
|
it('returns valid JSON for simple objects', () => {
|
||||||
|
const result = safeStringify({ ok: true, data: { id: 'p_123' } });
|
||||||
|
expect(() => JSON.parse(result)).not.toThrow();
|
||||||
|
expect(JSON.parse(result)).toEqual({ ok: true, data: { id: 'p_123' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('escapes double quotes in string values', () => {
|
||||||
|
const result = safeStringify({ text: 'He said "hello"' });
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.text).toBe('He said "hello"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('escapes backslashes in string values', () => {
|
||||||
|
const result = safeStringify({ path: 'C:\\Users\\test' });
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.path).toBe('C:\\Users\\test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('escapes control characters (newline, tab)', () => {
|
||||||
|
const result = safeStringify({ text: 'line1\nline2\tindented' });
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.text).toBe('line1\nline2\tindented');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles unicode characters', () => {
|
||||||
|
const result = safeStringify({ text: '你好世界 🌍' });
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.text).toBe('你好世界 🌍');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles HTML-like content without breaking JSON', () => {
|
||||||
|
const html = '<div class="main">Hello</div>';
|
||||||
|
const result = safeStringify({ html });
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.html).toBe(html);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty string and null', () => {
|
||||||
|
expect(JSON.parse(safeStringify({ a: '' }))).toEqual({ a: '' });
|
||||||
|
expect(JSON.parse(safeStringify({ a: null }))).toEqual({ a: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles arrays with special characters', () => {
|
||||||
|
const result = safeStringify({ items: ['a"b', 'c\\d', 'e\nf'] });
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.items).toEqual(['a"b', 'c\\d', 'e\nf']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isValidJson', () => {
|
||||||
|
it('returns true for valid JSON', () => {
|
||||||
|
expect(isValidJson('{"ok":true}')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for invalid JSON', () => {
|
||||||
|
expect(isValidJson('{ok:true}')).toBe(false);
|
||||||
|
expect(isValidJson('')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// JSON safe serialization / JSON 安全序列化
|
||||||
|
// Always use this instead of manual string concatenation for JSON output
|
||||||
|
|
||||||
|
export function safeStringify(obj: unknown): string {
|
||||||
|
return JSON.stringify(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidJson(str: string): boolean {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,3 +8,4 @@ export {
|
|||||||
type CanvasNoiseConfig,
|
type CanvasNoiseConfig,
|
||||||
type PermissionState,
|
type PermissionState,
|
||||||
} from './types/fingerprint.js';
|
} from './types/fingerprint.js';
|
||||||
|
export { safeStringify, isValidJson } from './escape.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user