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

> Discover how alingse/jsoncsv detects arrays by identifying sibling keys with sequential 0-based integer indices. Explore the source code for restoring nested JSON from flat data.

- Repository: [alingse/jsoncsv](https://github.com/alingse/jsoncsv)
- Tags: deep-dive
- Published: 2026-02-24

---

**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`](https://github.com/alingse/jsoncsv/blob/main/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`](https://github.com/alingse/jsoncsv/blob/main/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:

```python
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:

```python
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`](https://github.com/alingse/jsoncsv/blob/main/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`](https://github.com/alingse/jsoncsv/blob/main/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()`.