mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
🐛 fix(redis): 修复自动模式 JSON 大整数精度丢失
- 保留 Redis JSON 值中的大整数原始字面量 - 避免自动格式化时通过 JSON.stringify 改写超出安全范围的数字 - 补充自动模式大整数与字符串转义展示回归测试 Refs #400
This commit is contained in:
@@ -20,6 +20,26 @@ describe('redisValueDisplay', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves large integer literals when formatting json in auto mode', () => {
|
||||
const value = '{"subSessionIds":["java.util.ArrayList",[1494694751571226624]],"currentSubSessionId":1494694751571226624}';
|
||||
const formatted = formatRedisStringValue(value);
|
||||
|
||||
expect(formatted).toMatchObject({
|
||||
isBinary: false,
|
||||
isJson: true,
|
||||
encoding: 'UTF-8',
|
||||
});
|
||||
expect(formatted.displayValue).toContain('1494694751571226624');
|
||||
expect(formatted.displayValue).not.toContain('1494694751571226600');
|
||||
});
|
||||
|
||||
it('keeps json string escape rendering consistent in auto mode', () => {
|
||||
const formatted = formatRedisStringValue('{"name":"\\u4e2d\\u6587","id":1494694751571226624}');
|
||||
|
||||
expect(formatted.displayValue).toContain('"name": "中文"');
|
||||
expect(formatted.displayValue).toContain('"id": 1494694751571226624');
|
||||
});
|
||||
|
||||
it('falls back to hex for obvious binary values', () => {
|
||||
expect(formatRedisStringValue('\u0000\u0001\u0002abc')).toMatchObject({
|
||||
isBinary: true,
|
||||
|
||||
@@ -88,13 +88,135 @@ const tryDecodeValue = (value: string): { displayValue: string; encoding: string
|
||||
return { displayValue: toHexDisplay(value), encoding: 'HEX', needsHex: true };
|
||||
};
|
||||
|
||||
const tryFormatJson = (value: string): { isJson: boolean; formatted: string } => {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return { isJson: true, formatted: JSON.stringify(parsed, null, 2) };
|
||||
} catch {
|
||||
return { isJson: false, formatted: value };
|
||||
const findNextNonWhitespace = (value: string, startIndex: number): string => {
|
||||
for (let i = startIndex; i < value.length; i++) {
|
||||
if (!/\s/.test(value[i])) {
|
||||
return value[i];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const readJsonStringToken = (value: string, startIndex: number): { token: string; nextIndex: number } => {
|
||||
let index = startIndex + 1;
|
||||
let escaped = false;
|
||||
while (index < value.length) {
|
||||
const char = value[index];
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (char === '\\') {
|
||||
escaped = true;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (char === '"') {
|
||||
return { token: value.slice(startIndex, index + 1), nextIndex: index + 1 };
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return { token: value.slice(startIndex), nextIndex: value.length };
|
||||
};
|
||||
|
||||
const readJsonPrimitiveToken = (value: string, startIndex: number): { token: string; nextIndex: number } => {
|
||||
let index = startIndex;
|
||||
while (index < value.length && !/[\s,\]}]/.test(value[index])) {
|
||||
index++;
|
||||
}
|
||||
return { token: value.slice(startIndex, index), nextIndex: index };
|
||||
};
|
||||
|
||||
const formatJsonStringToken = (token: string): string => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(token));
|
||||
} catch {
|
||||
return token;
|
||||
}
|
||||
};
|
||||
|
||||
const formatJsonPreservingNumberLiterals = (value: string): string | null => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const indentUnit = ' ';
|
||||
const indent = (depth: number) => indentUnit.repeat(Math.max(0, depth));
|
||||
let result = '';
|
||||
let depth = 0;
|
||||
let index = 0;
|
||||
let lastToken: 'open' | 'value' | 'close' | 'comma' | 'colon' | '' = '';
|
||||
|
||||
while (index < value.length) {
|
||||
const char = value[index];
|
||||
if (/\s/.test(char)) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"') {
|
||||
const { token, nextIndex } = readJsonStringToken(value, index);
|
||||
result += formatJsonStringToken(token);
|
||||
lastToken = 'value';
|
||||
index = nextIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '{' || char === '[') {
|
||||
const closeChar = char === '{' ? '}' : ']';
|
||||
result += char;
|
||||
depth++;
|
||||
lastToken = 'open';
|
||||
if (findNextNonWhitespace(value, index + 1) !== closeChar) {
|
||||
result += `\n${indent(depth)}`;
|
||||
}
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '}' || char === ']') {
|
||||
depth--;
|
||||
if (lastToken !== 'open') {
|
||||
result += `\n${indent(depth)}`;
|
||||
}
|
||||
result += char;
|
||||
lastToken = 'close';
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === ',') {
|
||||
result += `,\n${indent(depth)}`;
|
||||
lastToken = 'comma';
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === ':') {
|
||||
result += ': ';
|
||||
lastToken = 'colon';
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { token, nextIndex } = readJsonPrimitiveToken(value, index);
|
||||
result += token;
|
||||
lastToken = 'value';
|
||||
index = nextIndex;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const tryFormatJson = (value: string): { isJson: boolean; formatted: string } => {
|
||||
const formatted = formatJsonPreservingNumberLiterals(value);
|
||||
if (formatted !== null) {
|
||||
return { isJson: true, formatted };
|
||||
}
|
||||
return { isJson: false, formatted: value };
|
||||
};
|
||||
|
||||
export const toHexDisplay = (value: string): string => {
|
||||
|
||||
Reference in New Issue
Block a user