From eb184853619fc342afb4fc8cbb35d6a963faae9d Mon Sep 17 00:00:00 2001 From: tsukino <87639218+0xtsukino@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:16:14 +0800 Subject: [PATCH] Fix parser to return range for entire array when accessing array field (#212) When calling parser.ranges.body('a', {type: 'json'}) for a body like {a: [{b: 1}, {c: 2}]}, the parser now returns the range for the entire array value instead of returning empty. Changes: - Store array field with full range (key + value) before processing elements - hideKey option returns just the array value: [{b: 1}, {c: 2}] - hideValue option returns just the key: "a" Added tests for: - Extracting entire array value directly - hideKey/hideValue options for array fields - Array of primitives - Nested array fields (e.g., data.items) --- packages/plugin-sdk/src/parser.test.ts | 79 ++++++++++++++++++++++++++ packages/plugin-sdk/src/parser.ts | 28 ++++++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/packages/plugin-sdk/src/parser.test.ts b/packages/plugin-sdk/src/parser.test.ts index 19720e3..6e1dca5 100644 --- a/packages/plugin-sdk/src/parser.test.ts +++ b/packages/plugin-sdk/src/parser.test.ts @@ -929,6 +929,85 @@ describe('Parser', () => { expect(ranges).toHaveLength(0); }); + + it('should extract entire array value when accessing array field directly', () => { + const request = + 'POST /api HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + '\r\n' + + '{"a":[{"b":1},{"c":2}]}'; + + const parser = new Parser(request); + const ranges = parser.ranges.body('a', { type: 'json' }); + + expect(ranges).toHaveLength(1); + const extracted = request.substring(ranges[0].start, ranges[0].end); + // Should include the key and the entire array value + expect(extracted).toContain('"a"'); + expect(extracted).toContain('[{"b":1},{"c":2}]'); + }); + + it('should extract only array value with hideKey option', () => { + const request = + 'POST /api HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + '\r\n' + + '{"a":[{"b":1},{"c":2}]}'; + + const parser = new Parser(request); + const ranges = parser.ranges.body('a', { type: 'json', hideKey: true }); + + expect(ranges).toHaveLength(1); + const extracted = request.substring(ranges[0].start, ranges[0].end); + expect(extracted).toBe('[{"b":1},{"c":2}]'); + expect(extracted).not.toContain('"a"'); + }); + + it('should extract only array key with hideValue option', () => { + const request = + 'POST /api HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + '\r\n' + + '{"a":[{"b":1},{"c":2}]}'; + + const parser = new Parser(request); + const ranges = parser.ranges.body('a', { type: 'json', hideValue: true }); + + expect(ranges).toHaveLength(1); + const extracted = request.substring(ranges[0].start, ranges[0].end); + expect(extracted).toBe('"a"'); + expect(extracted).not.toContain('['); + }); + + it('should extract array of primitives', () => { + const request = + 'POST /api HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + '\r\n' + + '{"numbers":[1,2,3,4,5]}'; + + const parser = new Parser(request); + const ranges = parser.ranges.body('numbers', { type: 'json', hideKey: true }); + + expect(ranges).toHaveLength(1); + const extracted = request.substring(ranges[0].start, ranges[0].end); + expect(extracted).toBe('[1,2,3,4,5]'); + }); + + it('should extract nested array field', () => { + const request = + 'POST /api HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + '\r\n' + + '{"data":{"items":[1,2,3]}}'; + + const parser = new Parser(request); + const ranges = parser.ranges.body('data.items', { type: 'json', hideKey: true }); + + expect(ranges).toHaveLength(1); + const extracted = request.substring(ranges[0].start, ranges[0].end); + expect(extracted).toBe('[1,2,3]'); + }); }); describe('Mixed Paths (Objects and Arrays)', () => { diff --git a/packages/plugin-sdk/src/parser.ts b/packages/plugin-sdk/src/parser.ts index f92ba92..6f50594 100644 --- a/packages/plugin-sdk/src/parser.ts +++ b/packages/plugin-sdk/src/parser.ts @@ -425,7 +425,33 @@ export class Parser { // Handle different value types if (typeof value === 'object' && value !== null) { if (Array.isArray(value)) { - // Handle array + // Handle array - first store the array itself, then process elements + const valueStr = JSON.stringify(value); + const valueBytes = Buffer.from(valueStr, 'utf8'); + const valueByteIndex = textBytes.indexOf(valueBytes, actualValueByteStart); + + if (valueByteIndex !== -1) { + const valueByteEnd = valueByteIndex + valueBytes.length; + + // Store the array itself as a field + result[pathKey] = { + value: value, + ranges: { + start: baseOffset + keyByteIndex, + end: baseOffset + valueByteEnd, + }, + keyRange: { + start: baseOffset + keyByteIndex, + end: baseOffset + keyByteIndex + keyBytes.length, + }, + valueRange: { + start: baseOffset + valueByteIndex, + end: baseOffset + valueByteEnd, + }, + }; + } + + // Then recursively process array elements this.processJsonArray( value, textBytes,