Ver código fonte

feat(redis-inspector): enhance string inspection with JSON array handling

Dave 1 mês atrás
pai
commit
7e75d4e5ce

+ 157 - 0
apps/box-mgnt-api/src/mgnt-backend/feature/redis-inspector/redis-inspector.service.ts

@@ -143,6 +143,7 @@ export class RedisInspectorService {
   ): Promise<RedisInspectorRecordDetail> {
     const byteLen = await this.redisService.strLen(key);
     const rawValue = (await this.redisService.get(key)) ?? '';
+    const trimmedValue = rawValue.trim();
     const { text, truncated } = this.prepareStringPreview(rawValue);
     const meta: Record<string, any> = { byteLen };
     const preview: Record<string, any> = {
@@ -150,6 +151,34 @@ export class RedisInspectorService {
       data: text,
     };
 
+    if (byteLen > MAX_JSON_PARSE_BYTES && trimmedValue.startsWith('[')) {
+      const extracted = this.extractFirstJsonArrayElements(trimmedValue, 5);
+      if (extracted && extracted.length) {
+        preview.format = 'json';
+        preview.data = extracted.map((item) => {
+          try {
+            return JSON.parse(item);
+          } catch {
+            return item;
+          }
+        });
+        const totalItems = this.countTopLevelJsonArrayElements(trimmedValue);
+        if (totalItems !== null) {
+          meta.itemCount = totalItems;
+          preview.truncated = totalItems > extracted.length;
+        } else {
+          preview.truncated = true;
+        }
+        return {
+          key,
+          type: 'string',
+          ttlSec,
+          meta,
+          preview,
+        };
+      }
+    }
+
     const canParseJson = byteLen <= MAX_JSON_PARSE_BYTES;
     const candidate = canParseJson ? this.tryParseJson(rawValue) : undefined;
 
@@ -187,6 +216,134 @@ export class RedisInspectorService {
     };
   }
 
+  private countTopLevelJsonArrayElements(raw: string): number | null {
+    const trimmed = raw.trim();
+    if (!trimmed.startsWith('[')) {
+      return null;
+    }
+    let depth = 0;
+    let inString = false;
+    let escape = false;
+    let seenNonWhitespace = false;
+    let count = 0;
+    for (let i = 1; i < trimmed.length; i++) {
+      const char = trimmed[i];
+      if (inString) {
+        if (escape) {
+          escape = false;
+        } else if (char === '\\') {
+          escape = true;
+        } else if (char === '"') {
+          inString = false;
+        }
+        continue;
+      }
+
+      if (char === '"') {
+        inString = true;
+        seenNonWhitespace = true;
+        continue;
+      }
+
+      if (char === '{' || char === '[') {
+        depth++;
+        seenNonWhitespace = true;
+        continue;
+      }
+
+      if (char === '}' || char === ']') {
+        if (depth > 0) {
+          depth--;
+          continue;
+        }
+      }
+
+      if (depth === 0 && (char === ',' || char === ']')) {
+        if (seenNonWhitespace) {
+          count++;
+          seenNonWhitespace = false;
+        }
+        if (char === ']') {
+          break;
+        }
+        continue;
+      }
+
+      if (!/\s/.test(char)) {
+        seenNonWhitespace = true;
+      }
+    }
+
+    return count;
+  }
+
+  private extractFirstJsonArrayElements(
+    raw: string,
+    maxItems: number,
+  ): string[] | null {
+    if (!raw || !raw.startsWith('[')) {
+      return null;
+    }
+    const elements: string[] = [];
+    let depth = 0;
+    let inString = false;
+    let escape = false;
+    let elementStart: number | null = null;
+    for (let i = 1; i < raw.length && elements.length < maxItems; i++) {
+      const char = raw[i];
+      if (elementStart === null) {
+        if (/\s/.test(char)) {
+          continue;
+        }
+        if (char === ']') {
+          break;
+        }
+        elementStart = i;
+      }
+
+      if (inString) {
+        if (escape) {
+          escape = false;
+        } else if (char === '\\') {
+          escape = true;
+        } else if (char === '"') {
+          inString = false;
+        }
+        continue;
+      }
+
+      if (char === '"') {
+        inString = true;
+        continue;
+      }
+
+      if (char === '{' || char === '[') {
+        depth++;
+        continue;
+      }
+
+      if (char === '}' || char === ']') {
+        if (depth > 0) {
+          depth--;
+          continue;
+        }
+      }
+
+      if ((char === ',' || char === ']') && depth === 0) {
+        const segment = raw.slice(elementStart, i).trim();
+        if (segment) {
+          elements.push(segment);
+        }
+        elementStart = null;
+        if (char === ']') {
+          break;
+        }
+      }
+    }
+
+    return elements.length ? elements : null;
+  }
+
   private async inspectList(
     key: string,
     ttlSec: number,