# How the restore Function in jsontool.py Reconstructs JSON Objects

> Uncover how the restore function in jsontool.py reconstructs JSON objects by reversing flattening, parsing dot-notated keys, and rebuilding nested structures.

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

---

**The `restore` function in [`jsoncsv/jsontool.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/jsontool.py) reverses the flattening process by parsing dot-notated keys into hierarchical path components and recursively rebuilding nested dictionaries and lists.**

The `jsoncsv` library provides bidirectional conversion between nested JSON objects and flat, CSV-friendly dictionaries. While `expand` flattens structures into dot-separated keys, the `restore` function performs the inverse operation, reconstructing the original JSON hierarchy from these flattened mappings. This reconstruction logic handles complex edge cases including escaped separators, empty root keys, and automatic array detection.

## How restore Reconstructs JSON Step-by-Step

The `restore` function (defined in [`jsoncsv/jsontool.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/jsontool.py) lines 108‑122) operates in three distinct phases to convert a flattened dictionary back into its original nested structure.

### Step 1: Parsing Keys into Path Components

For each entry in the flattened dictionary, `restore` converts the string key back into a list of path components. The method of parsing depends on the `safe` parameter:

- **Standard mode (`safe=False`)**: The key is split on the separator (`.` by default) using `key.split(separator)`.
- **Safe mode (`safe=True`)**: The function calls `decode_safe_key(key, separator)` from [`jsoncsv/utils.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/utils.py) (lines 21‑41) to properly handle escaped separators and backslashes that appear within actual key names.

An empty key (`""`) represents the root level of the object, resulting in an empty path list `[]`.

### Step 2: Building the Leaf Specifications

After parsing, `restore` constructs a list of tuples called `leafs`, where each tuple contains `(path_list, value)`. This format mirrors the output structure used by `gen_leaf` during the `expand` operation, creating a consistent internal representation for the reconstruction pipeline.

### Step 3: Recursive Hierarchy Assembly with from_leaf

The core reconstruction occurs in the `from_leaf` helper function (lines 61‑90 of [`jsoncsv/jsontool.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/jsontool.py)). This recursive function:

1. **Groups entries** by their first path element.
2. **Determines container type**: If all first-level keys form a consecutive integer sequence (verified by `is_array_index`), the children are sorted and assembled into a Python `list` (array). Otherwise, they become a `dict`.
3. **Recurses** on remaining path components until reaching leaf values.

The resulting Python object (`origin`) reflects the original nested JSON structure and is returned to the caller.

## Handling Safe Separators and Escaped Characters

When working with JSON objects that contain the separator character within key names (e.g., a key named `"a.b"`), the standard splitting algorithm would fail. The `safe` parameter enables proper round-tripping through encoding functions in [`jsoncsv/utils.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/utils.py):

- **`encode_safe_key`**: Escapes literal dots and backslashes during the `expand` phase.
- **`decode_safe_key`**: Reverses the escaping during `restore`, ensuring keys like `"a.b"` remain intact rather than being split into `["a", "b"]`.

This mechanism is essential for lossless conversion when property names contain characters that match the separator.

## Practical Code Examples

### Basic Round-Trip Reconstruction

This example demonstrates the complete workflow from nested JSON to flattened dictionary and back:

```python
from jsoncsv.jsontool import expand, restore

original = {
    "user": {"id": 1, "name": "Alice"},
    "items": [{"sku": "A1"}, {"sku": "B2"}],
}

flat = expand(original)          

# {'user.id': 1, 'user.name': 'Alice', 'items.0.sku': 'A1', 'items.1.sku': 'B2'}

reconstructed = restore(flat)    

# {'user': {'id': 1, 'name': 'Alice'}, 'items': [{'sku': 'A1'}, {'sku': 'B2'}]}

assert original == reconstructed

```

### Safe Mode with Dots in Keys

When your JSON contains literal dots in property names, use `safe=True` to prevent misinterpretation:

```python
from jsoncsv.jsontool import expand, restore

data = {"a.b": {"c": 5}}        # Key contains a literal dot

flat = expand(data, separator=".", safe=True)   # Key becomes "\.a\.b.c"

restored = restore(flat, separator=".", safe=True)
assert restored == data

```

### Custom Separator Support

You can specify alternative separators to avoid conflicts with your data:

```python
sep = "/"
flat = {"root/child": 42}
restored = restore(flat, separator=sep)

# Result: {'root': {'child': 42}}

```

## Summary

- The `restore` function in [`jsoncsv/jsontool.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/jsontool.py) (lines 108‑122) reconstructs nested JSON by reversing the flattening process.
- Path parsing supports both simple string splitting and safe decoding via `decode_safe_key` in [`jsoncsv/utils.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/utils.py) (lines 21‑41).
- The `from_leaf` helper (lines 61‑90) recursively assembles structures, automatically detecting arrays using `is_array_index`.
- Empty keys are treated as root-level assignments, enabling reconstruction of top-level scalar values.
- The `safe` parameter ensures lossless round-tripping when key names contain separator characters.

## Frequently Asked Questions

### What is the difference between expand and restore in jsoncsv?

`expand` flattens nested JSON objects into single-level dictionaries with dot-separated keys, while `restore` performs the inverse operation by parsing those keys back into hierarchical structures. According to the `jsoncsv` source code, `restore` uses the `from_leaf` recursive function to rebuild lists and dictionaries from the flattened representation.

### How does restore handle JSON arrays?

The `restore` function detects arrays by checking if all first-level keys in a group form a consecutive integer sequence using the `is_array_index` helper. When this condition is met, `from_leaf` sorts the children by their integer indices and returns them as a Python `list` rather than a dictionary, accurately reconstructing the original array structure.

### What is the safe parameter in restore used for?

The `safe` parameter controls how keys containing literal separator characters are handled. When `safe=True`, `restore` calls `decode_safe_key` from [`jsoncsv/utils.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/utils.py) to properly interpret escaped separators and backslashes. This prevents keys like `"user.name"` from being split into `["user", "name"]` when the dot is actually part of the property name rather than a path delimiter.

### Where is the core restoration logic implemented?

The primary restoration logic resides in [`jsoncsv/jsontool.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/jsontool.py). The `restore` function itself occupies lines 108‑122, while the recursive reconstruction engine `from_leaf` is implemented in lines 61‑90. Key parsing utilities for safe mode are located in [`jsoncsv/utils.py`](https://github.com/alingse/jsoncsv/blob/main/jsoncsv/utils.py) (lines 21‑41).