Summary
The CSV/JSON Converter recently improved multiline CSV parsing and lossy numeric inference, but the JSON-to-CSV path still has unsafe edge cases for JSON arrays containing null, primitives, or mixed row shapes.
The current branch chooses the object-flattening path based only on the first array item:
if(typeofparsed[0]==="object"&&!Array.isArray(parsed[0])){constflattened=parsed.map((item)=>flattenObject(itemasRecord<string,unknown>))...}Because typeof null === "object", an input like [null] enters the object-flattening path and can throw from Object.keys(null). If the first row is an object and later rows are null or primitives, those later rows are also cast to objects and processed incorrectly.
Why this matters
JSON arrays from APIs and logs are not always homogeneous object arrays. They can contain:
null rows;- primitive values;
- mixed objects and primitives;
- arrays mixed with objects;
- sparse or partially malformed exported data.
The converter should either handle these safely or return an intentional, actionable validation error. It should not crash with an internal TypeError or silently drop rows.
Affected files
Likely affected:
src/features/tools/csv-json-converter/logic.tssrc/features/tools/csv-json-converter/csv-json-task-logic.tstests/unit/csv-json-task-logic.test.tstests/unit/csv-json-task.test.ts- Pipeline adapter coverage if CSV/JSON conversion is used in Pipeline Builder
Current behavior
Relevant implementation:
jsonToCsv() parses JSON.- It requires the root value to be an array.
- If
parsed[0] is an object and not an array, it treats every row as an object and calls flattenObject(). flattenObject() assumes Object.keys(obj) is safe.
Problem cases:
[{"id":1}, 2, "x", false]Expected behavior should be explicit and stable for each of these.
Expected behavior
Choose and document one of these models.
Option A: strict object-array mode
When includeHeader is true and the converter is treating input as object rows:
- require every row to be a non-null plain object;
- reject arrays, primitives, and
null rows with an actionable error like:
JSON array rows must be objects when converting to header-based CSV. Row 2 is null.
Option B: permissive mixed-row mode
Support mixed rows explicitly:
- object rows are flattened;
- primitive/null rows are serialized under a reserved column such as
value; - array rows are serialized as JSON under
value; - collisions with an existing
value key are handled deterministically.
For this product, Option A is safer and easier to explain.
Suggested implementation plan
- Add an
isPlainObjectRow() helper:
functionisPlainObjectRow(value: unknown): value is Record<string,unknown>{returnBoolean(value)&&typeofvalue==="object"&&!Array.isArray(value)}- In
jsonToCsv(), decide the conversion mode by validating the full array, not just parsed[0]. - If using strict mode, throw a named error for non-object rows when object-header CSV is requested.
- Keep existing primitive-array conversion behavior for arrays that are entirely primitive or arrays.
- Add tests for:
[null] does not crash;[{"id":1}, null] returns an actionable validation error;- primitive arrays still convert predictably;
- arrays of arrays still convert predictably;
- nested arrays/objects inside object cells remain JSON-safe.
- Update UI error copy if the raw error message is exposed.
Acceptance criteria
Related code pointers
src/features/tools/csv-json-converter/logic.tssrc/features/pipeline/adapter-registry.tstests/unit/csv-json-task-logic.test.ts
Summary
The CSV/JSON Converter recently improved multiline CSV parsing and lossy numeric inference, but the JSON-to-CSV path still has unsafe edge cases for JSON arrays containing
null, primitives, or mixed row shapes.The current branch chooses the object-flattening path based only on the first array item:
Because
typeof null === "object", an input like[null]enters the object-flattening path and can throw fromObject.keys(null). If the first row is an object and later rows arenullor primitives, those later rows are also cast to objects and processed incorrectly.Why this matters
JSON arrays from APIs and logs are not always homogeneous object arrays. They can contain:
nullrows;The converter should either handle these safely or return an intentional, actionable validation error. It should not crash with an internal TypeError or silently drop rows.
Affected files
Likely affected:
src/features/tools/csv-json-converter/logic.tssrc/features/tools/csv-json-converter/csv-json-task-logic.tstests/unit/csv-json-task-logic.test.tstests/unit/csv-json-task.test.tsCurrent behavior
Relevant implementation:
jsonToCsv()parses JSON.parsed[0]is an object and not an array, it treats every row as an object and callsflattenObject().flattenObject()assumesObject.keys(obj)is safe.Problem cases:
[null][{"id":1}, null][{"id":1}, 2, "x", false]Expected behavior should be explicit and stable for each of these.
Expected behavior
Choose and document one of these models.
Option A: strict object-array mode
When
includeHeaderis true and the converter is treating input as object rows:nullrows with an actionable error like:Option B: permissive mixed-row mode
Support mixed rows explicitly:
value;value;valuekey are handled deterministically.For this product, Option A is safer and easier to explain.
Suggested implementation plan
isPlainObjectRow()helper:jsonToCsv(), decide the conversion mode by validating the full array, not justparsed[0].[null]does not crash;[{"id":1}, null]returns an actionable validation error;Acceptance criteria
[null]and mixed arrays no longer produce uncaught TypeError-style failures.npm test -- --run tests/unit/csv-json-task-logic.test.ts tests/unit/csv-json-task.test.tspasses.npm run check:typesandnpm run lintpass.Related code pointers
src/features/tools/csv-json-converter/logic.tssrc/features/pipeline/adapter-registry.tstests/unit/csv-json-task-logic.test.ts