Skip to content

[P1][Correctness] CSV/JSON Converter must handle null, primitive, and mixed JSON arrays safely #306

Description

@baixiangcpp

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.ts
  • src/features/tools/csv-json-converter/csv-json-task-logic.ts
  • tests/unit/csv-json-task-logic.test.ts
  • tests/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:

[null]
[{"id":1}, null]
[{"id":1}, 2, "x", false]
[null, {"id":1}]

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

  1. Add an isPlainObjectRow() helper:
functionisPlainObjectRow(value: unknown): value is Record<string,unknown>{returnBoolean(value)&&typeofvalue==="object"&&!Array.isArray(value)}
  1. In jsonToCsv(), decide the conversion mode by validating the full array, not just parsed[0].
  2. If using strict mode, throw a named error for non-object rows when object-header CSV is requested.
  3. Keep existing primitive-array conversion behavior for arrays that are entirely primitive or arrays.
  4. 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.
  5. Update UI error copy if the raw error message is exposed.

Acceptance criteria

  • [null] and mixed arrays no longer produce uncaught TypeError-style failures.
  • Object-row mode validates every row before flattening.
  • Error messages identify the row index and expected row shape.
  • Existing multiline CSV and numeric-inference tests still pass.
  • JSON-safe serialization for nested arrays/objects remains intact.
  • npm test -- --run tests/unit/csv-json-task-logic.test.ts tests/unit/csv-json-task.test.ts passes.
  • Pipeline adapter tests still pass if CSV/JSON conversion is exposed there.
  • npm run check:types and npm run lint pass.

Related code pointers

  • src/features/tools/csv-json-converter/logic.ts
  • src/features/pipeline/adapter-registry.ts
  • tests/unit/csv-json-task-logic.test.ts

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions