JSON import/export¶
The richeditor-compose-json module serializes editor content to a stable, versioned JSON format built on the document model. Unlike HTML or Markdown, the JSON format is lossless for everything the editor supports, canonical (the same content always produces the same string), and safe to store, compare, and migrate.
Installation¶
Use the same version as richeditor-compose.
Basic usage¶
val state = rememberRichTextState()
// Export
val json: String = state.toJson()
// Import
state.setJson(json)
These two extensions are the whole public API of the module. To work with the structural snapshot instead of a state, combine them with the document model: state.toRichTextDocument() after setJson, or setRichTextDocument before toJson.
Format¶
The envelope is {"v": 1, "blocks": [...]} where v is the schema version. The version this library reads and writes is exposed as CURRENT_JSON_SCHEMA_VERSION, so stored documents can be checked for compatibility before calling setJson. Example:
{
"v": 1,
"blocks": [
{"id": "b0", "type": "heading", "level": 1, "text": "Title", "spans": []},
{"id": "b1", "type": "list-item", "ordered": true, "indent": 0, "text": "One", "spans": []},
{
"id": "b2",
"type": "paragraph",
"align": "center",
"text": "Hello bold link",
"spans": [
{"k": "bold", "r": [6, 9]},
{"k": "link", "r": [11, 14], "url": "https://example.com"}
]
}
]
}
Block fields:
| Field | When present | Meaning |
|---|---|---|
id | always | "b" + index; accepted and ignored on import |
type | always | paragraph, heading, or list-item |
level | headings | Heading level 1 to 6 |
ordered, indent, start | list items | Ordered flag, 0-based nesting, numbering restart |
align, dir, lineHeight, textIndent | when set | Paragraph style |
br | line-break paragraphs | Paragraph created by <br> |
text, spans | always | Content and styling marks |
Each mark is {"k": kind, "r": [first, last], ...} with an inclusive range. Kinds: bold, italic, underline, strike, code, link (url), color and highlight (argb as 8-char uppercase hex), font-size and letter-spacing (value, unit), font-weight (value), baseline-shift (value), shadow (argb, x, y, blur), image (url, width, height, alt), token (trigger, id, label).
Stability guarantees¶
- Versioned: every document carries
"v". Documents from newer schema versions are rejected withUnsupportedRichTextJsonVersionExceptioninstead of being decoded lossily. - Canonical: the same content always encodes to the same string, so JSON strings can be compared for equality in tests and caches.
- Idempotent round-trip:
toJsonaftersetJsonreturns the identical string for any document made of editor-representable content (everything in the tables above exceptUnknownmarks). - Unknown mark kinds are tolerated, not preserved: a document from a newer or extended producer decodes without failing, but the editor cannot represent unknown marks, so
setJsonfollowed bytoJsondrops them. To keep forward-compatible data intact, store the original JSON and treat the editor as a consumer. - Custom marks serialize through the registry: a
RichTextSpanMark.Customis encoded as{"k": kind, "r": [...], "attrs": {...}}when a descriptor registered on the state'sspanStyleRegistryopts into the JSON format, and skipped otherwise. On decode, unregistered custom kinds degrade toUnknown. See Custom span styles. - Strict validation: structurally invalid input (non-finite numbers, out-of-range font weights, malformed
argb, negative indents) fails fast withMalformedRichTextJsonExceptionrather than producing a document that cannot be re-encoded.startmay be negative, matching the HTMLstartattribute. - v2 compatible: the envelope, block fields, and the core mark kinds (
boldthroughhighlight) match the upcoming richeditor v2 document format, so stored v1 documents remain readable there.
Error handling¶
try {
state.setJson(json)
} catch (e: MalformedRichTextJsonException) {
// Structurally invalid input
} catch (e: UnsupportedRichTextJsonVersionException) {
// Written by a newer library version (e.documentVersion)
}
Both extend IllegalArgumentException.