How jsoncsv Handles Type Detection for Restoring Arrays: A Deep Dive into the Source Code

When converting flat key-value pairs back to nested JSON, jsoncsv detects arrays by checking if sibling keys form a complete sequence of 0-based integer indices.

The jsoncsv library provides bidirectional transformation between nested JSON objects and flattened CSV-compatible dictionaries. When restoring arrays from the flattened representation, the library must distinguish between JSON objects (Python dictionaries) and JSON arrays (Python lists) without explicit type metadata. This reconstruction logic is implemented in the core transformation module and relies on a strict contiguity check of index keys.

The Restoration Pipeline

The restoration process begins when you call restore() on a flattened dictionary. The library first converts the flat keys into hierarchical path components, then recursively rebuilds the original structure.

From Flat Keys to Leaf Nodes

In jsoncsv/jsontool.py, the restore() function initiates the process by splitting each key into a path sequence. Using either decode_safe_key (for safe mode) or standard string splitting, it generates a list of (path, value) tuples called leafs that represent the terminal nodes of the JSON tree structure.

Recursive Reconstruction with from_leaf

The from_leaf helper function groups these leaf nodes by their first path component and recursively processes the remaining path segments. At each level of recursion, it collects child_keys—the set of immediate sibling keys under the current path prefix. This set becomes the critical input for type detection.

Array Detection Logic in jsoncsv

The decision between creating a list or a dict hinges entirely on whether the sibling keys represent a valid array index sequence.

The is_array_index Helper Function

Located in jsoncsv/jsontool.py, the is_array_index function implements the core type detection algorithm. It receives a set of child keys and performs a strict validation:

  1. It builds a key_map set from the supplied sibling keys
  2. It generates the expected integer range from 0 to len(keys)-1
  3. It returns True only if every integer in that expected range exists in the key map

If enable_str=True is configured, the function also accepts string equivalents of integers (e.g., "0", "1") as valid indices, allowing flexibility for string-keyed flat representations.

Strict Contiguity Requirements

When is_array_index(child_keys) evaluates to True, the from_leaf function sorts the children by their integer index and returns a Python list containing the child values in order. If the check fails—meaning the keys are non-numeric, non-contiguous, or missing indices—the function returns a standard Python dict instead.

This means that a group of keys like {"0", "1", "2"} becomes an array, while {"0", "2"} (missing index 1) becomes a dictionary with string keys "0" and "2".

Practical Examples of Array Restoration

The following examples demonstrate how jsoncsv reconstructs arrays from flattened data and handles edge cases where array detection fails.

Successful Array Reconstruction

When indices form a complete 0-based sequence, restore() correctly rebuilds the JSON array:

from jsoncsv.jsontool import expand, restore

# Original JSON containing an array

data = {"users": [{"name": "Alice"}, {"name": "Bob"}]}

# Flatten the structure

flat = expand(data)          # {"users.0.name": "Alice", "users.1.name": "Bob"}

# Restore the original structure

restored = restore(flat)     # {"users": [{"name": "Alice"}, {"name": "Bob"}]}

print(restored == data)      # True

Fallback to Object for Irregular Keys

If the flattened keys contain gaps or non-sequential indices, the restoration process treats them as object keys rather than array indices:

from jsoncsv.jsontool import restore

# Missing index 1 breaks the sequence

flat = {"users.0.name": "Alice", "users.2.name": "Charlie"}

restored = restore(flat)
print(restored)

# Output: {'users': {'0': {'name': 'Alice'}, '2': {'name': 'Charlie'}}}

In this case, is_array_index returns False because index 1 is missing from the key set, causing from_leaf to return a dictionary with string keys "0" and "2" rather than a three-element list.

Summary

  • Path splitting: The restore() function in jsoncsv/jsontool.py splits flat keys into hierarchical paths using decode_safe_key or string splitting.
  • Contiguity check: The is_array_index helper validates that sibling keys form a complete sequence from 0 to N-1, optionally accepting string representations when enable_str=True.
  • Type inference: Based on the is_array_index result, from_leaf returns either a sorted list (for valid arrays) or a dict (for objects).
  • Data integrity: This detection mechanism ensures that only strictly sequential numeric keys reconstruct as arrays, preventing data corruption when original JSON objects happen to have numeric-looking keys.

Frequently Asked Questions

How does jsoncsv distinguish between a JSON object with numeric keys and a JSON array?

According to the jsoncsv/jsontool.py source code, the library applies a strict contiguity test via is_array_index. If the sibling keys under a path form a complete sequence starting at 0 with no gaps (e.g., 0, 1, 2), the structure becomes a list. If any index is missing or non-numeric, the structure becomes a dictionary, even if the keys look like numbers.

What happens if my flattened data has string indices like "0", "1" instead of integers?

The is_array_index function supports string indices when the enable_str parameter is set to True. It checks for the presence of both integer values and their string equivalents in the key_map. If all expected indices are present as strings (and enable_str=True), the restoration process treats them as array indices and returns a list.

Why did my array become a dictionary after restoring with jsoncsv?

This occurs when the flattened keys fail the contiguity check in is_array_index. Common causes include missing indices (e.g., having keys 0 and 2 but not 1) or using non-sequential numbering. The from_leaf function defaults to creating a dictionary whenever the keys do not represent a perfect 0-based sequence, ensuring data safety over aggressive array assumptions.

Can I force jsoncsv to treat specific keys as arrays during restoration?

The current implementation in jsoncsv does not provide a forced-type parameter in the public restore() API. Type detection is strictly automatic based on the is_array_index logic. To ensure correct array restoration, you must guarantee that your flattened data contains complete, sequential indices (0 through N-1) for all array elements before calling restore().

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →