Documentation

Core Concepts

This page explains how NanoJSON models a JSON document as a live, editable DOM tree, and documents the type system's actual behavior — including its edge cases.

JSONEditor: the Root Controller

JSONEditor (src/model/JSONEditor.js) is the sole public entry point. On construction it:

  1. Injects a stylesheet based on config.css (loadCSS)
  2. Creates a Lifecycle instance wired to the when.* callbacks
  3. Loads the initial data via getJSON() (json / file / path)
  4. Recursively converts the loaded data into a JSONEditorNode array via #jsonToChildren()
  5. Mounts to the existing element referenced by config.id, or creates a new <section class="pd-json-editor">

If, after loading, children.length < 1, it automatically calls insert() once to add an empty node (JSONEditor.js:153-155).

JSONEditorNode: the Recursive Node

JSONEditorNode (src/model/JSONEditorNode.js) represents a single key/value pair or array item, holding key / type / value / children / collapsed. Each node's DOM is rendered locally by #create() and cached in the private field #dom; adding (#add) or removing (#remove) a child only replaces the affected subtree instead of re-rendering the whole tree.

Type System and Edge Cases

Types are derived at runtime by getType() (src/data.js) based on the JavaScript value:

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"

getType(null) returns "object", because typeof null === "object" in JavaScript. Whether a null value actually renders as a null-typed node depends on how the caller handles that return value — and the two call sites handle it inconsistently:

In practice: importing {"a": null} renders a node whose type dropdown shows null; importing [null] renders a string node containing the literal text "null". If you need array elements to preserve a true null type, you must handle this discrepancy at the application layer.

Serialization: json Getter vs. export()

JSONEditorNode.json (a getter) recursively calls the private #json(), returning the corresponding JavaScript value per node type (array returns an array, object returns an object, boolean/number are coerced, everything else returns a string).

Root-level serialization has two paths, and they do not behave identically:

Path Filter Condition (JSONEditor.js) Behavior
editor.json (getter, lines 267-277) Only includes children where e.key is truthy Keyless children (e.g. when the root is an array, or a single scalar) are dropped
editor.export() (lines 289-310) Includes children where e.key is truthy, or when children.length === 1 A single keyless root child (array root, scalar root) is still written to the exported file

In other words: if the root is currently an array (type === "array") with exactly one keyless child, editor.json returns "{}", but editor.export() still downloads the correct data. Be aware of this discrepancy before relying on editor.json when the root type might be an array.

Read-Only Mode

readonly is not a flag propagated to every node individually — after the initial render, it's applied by running document.querySelectorAll(selectorSubNodes) (button:not(.collapsed), textarea, input, select) over the whole already-rendered DOM tree and batch-toggling the disabled attribute (see enable() / disable(), JSONEditor.js:171-183). Toggling read-only does not trigger any Lifecycle hook.

Further Reading

中文