# Type System

This page explains how NanoJSON derives a node's type from a JavaScript value, how each type is edited and serialized, what happens to an existing value on a type switch, and the `null` edge cases.

## Type Derivation

On import, the type is derived at runtime by `getType()` (`src/function/getType.js`):

| JS Value | `getType()` Returns |
|---|---|
| `Array.isArray(v)` is true | `"array"` |
| `typeof v === "object"` (**including `null`**) | `"object"` |
| `typeof v === "boolean"` | `"boolean"` |
| `typeof v === "number"` | `"number"` |
| anything else | `"string"` |

## Per-Type Behavior

| Type | Input | Serialized As | Notes |
|---|---|---|---|
| `string` | `textarea`, multi-line | The raw string | — |
| `number` | `textarea`, placeholder `NUM` | `Number(value)` | Input that can't parse as a number clears the field (except `-` and `.`, so negatives and decimals can be typed); an empty value serializes as `0` |
| `boolean` | `true` / `false` dropdown | `value.toLowerCase() === "true"` | An empty value is set to `"true"` |
| `object` | No value input; shows children | Object | Unlimited nesting |
| `array` | No value input; children shown by index with no `key` input | Array | Same |
| `null` | — | — | Only produced by import; see "`null` Edge Cases" below |

## Switching Types

The type dropdown (`src/function/typeSelect.js:25`) offers only `string` / `number` / `boolean` / `array` / `object` — `null` **cannot** be selected manually. On a switch, the existing value is handled per target type (`typeSelect.js:30-51`):

| Switch To | Effect on the Existing Value |
|---|---|
| `object` / `array` | `value` is cleared; if there are no children, one is added automatically (through `#add`, see [Core Concepts](/core-concepts#how-node-settings-are-inherited)) |
| `number` | `parseFloat` on the existing string; cleared if unparseable. `children` are **not** cleared, so switching back to `object` / `array` restores them |
| `string` / `boolean` | `value` and `children` are both cleared |

After a switch, `updateChild()` rebuilds the node's DOM and triggers `Lifecycle.update`, then focus moves to the `value-{id}` element.

## `null` Edge Cases

`getType(null)` returns `"object"` (JavaScript's `typeof null === "object"`), and the two call sites handle it inconsistently:

- **Object child values** (`JSONEditor.js:227-234`): when `value === null`, the node's type is overridden to `"null"` and `value` set to `null`.
- **Array child items** (`JSONEditor.js:205-209`): no equivalent handling. `getType()` returns `"object"`, but `e != null` is false, so it falls through to the `else` branch and is stored via `String(e)` as the text `"null"`, keeping the default `"string"` type.

The actual results:

| Imported Data | Result |
|---|---|
| `[null]` | Renders a **string** node containing the text `"null"`; serializes back as the string `"null"` |
| `{"a": null}` | While rendering that node, `showValue()` in `valueInput()` calls `.replace` on `null` (`src/function/valueInput.js:56`, `:80`) and throws a TypeError; the initial render aborts and `rendered` never fires |

If your data may contain `null`, convert it at the application layer (for example, to an empty string) before passing it to NanoJSON.

## Further Reading

- How each type is assembled back into JSON: [Serialization](/core-concepts-serialization)
- The `type` / `value` fields of `JSONEditorNode`: [API Reference: JSONEditorNode](/api-reference-node)
