Skip to content
Anjunar/ DOCS

JSON

The wire format for storing documents: a versioned envelope, one codec per node type, and full validation of untrusted payloads.

Envelope

Nodes as an array

Nodes are listed, not keyed by ID. A keyed object would silently merge duplicate IDs; as an array a duplicate stays visible and becomes DuplicateNodeId.

JSON, live

Encoded from the document on every commit.

A small document

A paragraph with strong and emphasized text, a link and inline code.

  1. First item

  2. Second item

20 words · 21 nodes
{ "format": "ember-document", "formatVersion": 1, "schemaVersion": 1, "root": "document", "nodes": [ { "id": "document", "type": "ember.core.root/1", "codecVersion": 1, "children": [ "json2", "json13", "json20" ] }, { "id": "json2", "type": "ember.rich-text.heading/1", "codecVersion": 1, "level": 2, "children": [ "json1" ] }, { "id": "json1", "type": "ember.core.text/1", "codecVersion": 1, "text": "A small document" }, { "id": "json13", "type": "ember.rich-text.paragraph/1", "codecVersion": 1, "children": [ "json3", "json4", "json5", "json6", "json7", "json9", "json10", "json11", "json12" ] }, { "id": "json3", "type": "ember.core.text/1", "codecVersion": 1, "text": "A paragraph with " }, { "id": "json4", "type": "ember.core.text/1", "codecVersion": 1, "text": "strong", "marks": [ { "mark": "ember.rich-text.strong/1" } ] }, { "id": "json5", "type": "ember.core.text/1", "codecVersion": 1, "text": " and " }, { "id": "json6", "type": "ember.core.text/1", "codecVersion": 1, "text": "emphasized", "marks": [ { "mark": "ember.rich-text.emphasis/1" } ] }, { "id": "json7", "type": "ember.core.text/1", "codecVersion": 1, "text": " text, a " }, { "id": "json9", "type": "ember.link.link/1", "codecVersion": 1, "href": "https://anjunar.com", "children": [ "json8" ] }, { "id": "json8", "type": "ember.core.text/1", "codecVersion": 1, "text": "link" }, { "id": "json10", "type": "ember.core.text/1", "codecVersion": 1, "text": " and " }, { "id": "json11", "type": "ember.core.text/1", "codecVersion": 1, "text": "inline code", "marks": [ { "mark": "ember.rich-text.inline-code/1" } ] }, { "id": "json12", "type": "ember.core.text/1", "codecVersion": 1, "text": "." }, { "id": "json20", "type": "ember.list.list/1", "codecVersion": 1, "kind": "ordered", "tight": true, "children": [ "json16", "json19" ] }, { "id": "json16", "type": "ember.list.item/1", "codecVersion": 1, "children": [ "json15" ] }, { "id": "json15", "type": "ember.rich-text.paragraph/1", "codecVersion": 1, "children": [ "json14" ] }, { "id": "json14", "type": "ember.core.text/1", "codecVersion": 1, "text": "First item" }, { "id": "json19", "type": "ember.list.item/1", "codecVersion": 1, "children": [ "json18" ] }, { "id": "json18", "type": "ember.rich-text.paragraph/1", "codecVersion": 1, "children": [ "json17" ] }, { "id": "json17", "type": "ember.core.text/1", "codecVersion": 1, "text": "Second item" } ] }
scala
val codecs = StandardJsonSupport.everything(media = MediaUrlPolicy.default) val json = DocumentJson.encodeToString(document, codecs) // Either[Vector[EncodeError], String] val decoded = DocumentJson.decodeString(json.toOption.get, schema, codecs) // Either[Vector[DecodeError], DecodeResult]
Versions

Three numbers, three responsibilities

A newer payload is rejected outright: it may carry fields this build does not understand, and reading it as an older one would lose data silently.

formatVersion
The envelope's shape. Never migrated; a newer format needs a newer reader.
schemaVersion
The application's document structure. Lifted by SchemaMigrations.
codecVersion
The fields of one node type. A codec may read older versions of itself.
A document, not a session

The payload has no selection, history or revision. What gets stored is the document, not the state of an edit in progress.