From path keys to structure
Flat key-value data is everywhere: translation catalogs ("checkout.button.pay": "Pay now"), exported feature flags, form posts with names like address[city], Spring and Helm style settings, or the output of the Flatten JSON tool. Unflattening splits each key on the delimiter and builds the objects and arrays those paths imply, so {"db.host": "x", "db.port": 5432} becomes {"db": {"host": "x", "port": 5432}}.
Keys are processed in order, and the nested output keeps that first-seen order. Values are copied verbatim, including number spellings that a JavaScript double would round.
When numeric segments become arrays
A segment such as 0 might be an array position or an ordinary key; for example, {"scores.1001": 7} is clearly keyed by an ID. The Arrays option decides:
- Numeric segments (default): a group of siblings becomes an array only if every one of them is a whole number without leading zeros and together they are exactly 0, 1, 2 … n-1.
items.0anditems.1make a two-item array;items.0anditems.2stay an object with keys"0"and"2". Items are placed by index, so the input order does not matter. - Brackets: only
[n]marks a position (items[0].sku); plain digits after a dot are keys. Sparse or mixed brackets are kept as object keys with a warning that points at the first key involved. - Never: every segment is a key and no arrays are created.
Conflicts are errors, not overwrites
Some flat inputs describe two different things at one place. With "a": 1 and "a.b": 2, a cannot be both the number 1 and an object containing b. Many libraries silently keep whichever key came last; this tool stops and reports the second key with its line and column, naming the earlier key it clashes with. The same applies when two spellings lead to one path, such as a duplicated key, or x[0] and x.0 in bracket style. Rename one of the keys, or fix the export that produced them.
Escaped delimiters
A backslash keeps the next character inside the current name. "versions.v1\\.2" in JSON source (the key versions.v1\.2) yields {"versions": {"v1.2": …}}. A doubled backslash stands for one literal backslash, and with brackets \[ and \] are literal brackets. Before any other character a backslash is ordinary, so Windows paths such as C:\temp survive unchanged.
Delimiter offers a dot, slash, underscore or custom string and must match the one used when the data was flattened. An underscore splits names like first_name too, so prefer it only for data that was flattened with it.
Values inside a flat object
A value may itself be an object or array, for instance "meta": {} or "tags": ["a", "b"]. It is placed as-is at its path. Nothing else may then be nested under that same path, because merging two sources of truth silently is exactly what the tool avoids. Format or validate the result afterwards with the JSON formatter.
Examples
Translation catalog
Dotted message IDs from an i18n file become the nested structure that libraries such as i18next or vue-i18n load.
{
"checkout.title": "Your basket",
"checkout.button.pay": "Pay now",
"checkout.button.back": "Keep shopping",
"errors.card.declined": "Your card was declined",
"errors.network": "Check your connection"
}{
"checkout": {
"title": "Your basket",
"button": {
"pay": "Pay now",
"back": "Keep shopping"
}
},
"errors": {
"card": {
"declined": "Your card was declined"
},
"network": "Check your connection"
}
}
Bracket paths from a form or lodash
With bracket style, [0] and [1] rebuild the lines array. “order[id]” has no numeric index, so its brackets stay part of the key name.
{
"order[id]": "A-17",
"lines[0].sku": "KB-104",
"lines[0].qty": 1,
"lines[1].sku": "MS-220",
"lines[1].qty": 2
}{
"order[id]": "A-17",
"lines": [
{
"sku": "KB-104",
"qty": 1
},
{
"sku": "MS-220",
"qty": 2
}
]
}
Numeric IDs stay keys
User IDs 1001 and 1002 are not 0, 1, 2 …, so users stays an object, while matrix.0.0 and matrix.0.1 form nested arrays.
{
"users.1001.name": "Ada",
"users.1002.name": "Linus",
"matrix.0.0": 1,
"matrix.0.1": 2
}{
"users": {
"1001": {
"name": "Ada"
},
"1002": {
"name": "Linus"
}
},
"matrix": [
[
1,
2
]
]
}
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
"a.b" cannot be nested inside "a" (line 2), which already holds a value | One key sets a plain value at a path and a later key tries to put more keys under it. | Rename one of the two keys. If the dot is really part of the name, escape it as a.b. |
"a" sets a value where "a.b" (line 3) already nests other keys | The same clash in the opposite order: the nested key came first. | Decide which of the two the data should keep and remove the other. |
Unflattening needs a flat JSON object such as {"a.b": 1}, but the input is an array | The top level of the input is not an object, so there are no path keys to read. | Paste the object itself, or flatten the array first. |
The [n] indices under "lines" are not exactly 0, 1, 2 … or are mixed with names, so they were kept as object keys | Bracket indices skip a number or are mixed with named keys at the same level. | Fill the gaps if an array was intended; otherwise the object result is correct. |
Frequently asked questions
Why did my numbered keys not become an array?
Arrays are only built when the indices are exactly 0 to n-1. Gaps, leading zeros or a starting index of 1 would otherwise create holes or change meaning, so those stay object keys.
Does the order of the flat keys matter?
Object keys keep their first-seen order. Array items are placed by their index, so lines.1 may appear before lines.0 in the input.
Can I use a delimiter like double underscore?
Yes. Choose Custom and type __ or any other string without backslashes, digits or square brackets.
Is it safe for large exports?
Yes. The conversion runs in a background worker in your browser, handles deep paths without recursion, and the data is never uploaded.