Skip to content
Merged
Show file tree
Hide file tree
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
14 changes: 13 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Pretty-prints a JSON-like string without parsing.
* Fast path: chunked copying, fast string scan, lookahead for empty {} / [].
* Decodes \uXXXX unicode sequences and \/ forward slash escapes for readability.
*
* @param {string} input
* @param {string} indent
Expand Down Expand Up @@ -45,6 +46,7 @@ function fastJsonFormat(input, indent = ' ') {
// Character codes
const QUOTE = 34; // "
const BACKSLASH = 92; // \
const FORWARD_SLASH = 47;// /
const OPEN_BRACE = 123; // {
const CLOSE_BRACE = 125; // }
const OPEN_BRACKET = 91; // [
Expand Down Expand Up @@ -97,7 +99,7 @@ function fastJsonFormat(input, indent = ' ') {
};

// Scan a JSON string starting at index of opening quote `i` (s[i] === '"').
// Returns index just after the closing quote and decodes \uXXXX sequences.
// Returns index just after the closing quote and decodes \uXXXX and \/ sequences.
const scanString = (i) => {
out.push('"'); // opening quote
let j = i + 1;
Expand Down Expand Up @@ -134,6 +136,16 @@ function fastJsonFormat(input, indent = ' ') {
}
// If parsing failed, reset and let it be copied as-is
j = backslashPos + 1;
} else if (j < n && s.charCodeAt(j) === FORWARD_SLASH) {
// Found \/ - decode to / for readability
// Copy everything up to the backslash
if (backslashPos > lastCopy) {
out.push(s.slice(lastCopy, backslashPos));
}
out.push('/');
j++; // skip the forward slash
lastCopy = j;
continue;
}
// For other escapes (or invalid \u), just skip the escaped char
if (j < n) j++;
Expand Down
51 changes: 51 additions & 0 deletions tests/escaped.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,55 @@ describe('escaped characters', () => {
}`;
assertEqual(input, expected);
});
});

describe('forward slash escape sequences', () => {
it('should decode \\/ escape sequences to forward slashes', () => {
const input = '{"url":"https:\\/\\/example.com\\/api\\/v1"}';
const expected = `{
"url": "https://example.com/api/v1"
}`;
assertEqual(input, expected);
});

it('should handle unescaped forward slashes correctly', () => {
const input = '{"url":"https://example.com/api/v1"}';
const expected = `{
"url": "https://example.com/api/v1"
}`;
assertEqual(input, expected);
});

it('should handle forward slashes mixed with other escape sequences', () => {
const input = '{"text":"line1\\npath\\/to\\/file\\ttab","unicode":"\\u4e16\\u754c\\/path"}';
const expected = `{
"text": "line1\\npath/to/file\\ttab",
"unicode": "世界/path"
}`;
assertEqual(input, expected);
});

it('should handle a single escaped forward slash', () => {
const input = '{"slash":"\\/"}';
const expected = `{
"slash": "/"
}`;
assertEqual(input, expected);
});

it('should handle multiple consecutive escaped forward slashes', () => {
const input = '{"path":"\\/\\/network\\/share"}';
const expected = `{
"path": "//network/share"
}`;
assertEqual(input, expected);
});

it('should handle escaped forward slash at end of string', () => {
const input = '{"url":"https://example.com\\/"}';
const expected = `{
"url": "https://example.com/"
}`;
assertEqual(input, expected);
});
});