Value Coercion

📖 Table of Contents

SmarkForm normalizes imported values according to component type and shape. This can help with data-model changes, such as evolving a scalar field into a list. The rules below describe the supported conversions and their limits.

Scalar-to-Array List Coercion

When a list field receives a non-array value — a string, number, object, or null — it wraps that value in a single-item array. undefined is special: it resets the list to its current default instead.

This is particularly useful for model migrations: if a field that used to hold a single email string is upgraded to accept a list of emails, old saved data continues to work without any transformation step.

Exporting Empty Items

By default, empty items are not exported (export_empties: false). This keeps saved data clean by omitting blank rows.

Set "export_empties": true on the list to preserve empty slots — useful in draft-save workflows where you want to retain the user’s position in the list.

🔗
❓
<div id="myForm">
    <button data-smark='{"action":"removeItem","context":"email","preserve_non_empty":true}' title="Remove email">➖</button>
    <button data-smark='{"action":"addItem","context":"email"}' title="Add email">➕</button>
    <strong data-smark="label">Emails:</strong>
    <ul data-smark='{"type":"list","name":"email","of":"input","min_items":0}'>
        <li data-smark-role="empty_list">(No emails on record)</li>
        <li><input type="email" data-smark placeholder="name@example.com"></li>
    </ul>
</div>
#myForm ul {
    list-style: none;
    padding-left: 0;
}
const myForm = new SmarkForm(document.getElementById("myForm"), {
    "value": {
    "email": "alice@example.com"
}
});

👉 Scalar-to-array coercion: If you import a plain string instead of an array, SmarkForm automatically places it in a single-item list.

  • Click ⬇️ Export, change ["alice@example.com"] to just "alice@example.com" in the JSON playground editor below, then click ⬆️ Import — the single email is placed in the list automatically.
  • This mirrors the upgrade from a single-value field (e.g. "email") to a list field (e.g. "emails": [...] ).

👉 Empty items are not exported by default (controlled by export_empties).

  • Click ➕ to add a blank item, then ⬇️ Export — the blank row will be absent from the output, keeping saved data clean.
  • Set "export_empties": true on the list to keep blank slots in the data (useful in draft-save workflows where you want to preserve the user’s position in the list).

Number, Date, Time, and JSON Coercion

Fields with a specific HTML type automatically coerce values on both import and export:

  • <input type="number"> exports a JavaScript number (not a string), and accepts string representations on import (e.g. "28" → 28).
  • <input type="date"> exports an ISO 8601 string (YYYY-MM-DD), and accepts compact strings (YYYYMMDD) and Date objects on import.
  • <input type="time"> exports HH:MM:SS and accepts HH:MM on import.
  • Time fields also accept Date objects and epoch-millisecond numbers on import.
  • Value types with no natural empty state — number, date, time, datetime-local, color, radio, file… — export null when empty, to explicitly signal “unknown or indifferent” rather than "" or 0.
  • Plain text fields (<input type="text">, <textarea>) are the exception: they export an empty string (""), because a native text input does not distinguish “empty” from “no value”. A text field with format:"json" does export null when empty (see below).

JSON format

Adding {"format":"json"} (or the legacy alias encoding:"json") to an <input> or <textarea> enables JSON parsing and serialization: the field stores JSON text and exports a parsed JavaScript value.

Current limitation: the JSON field’s export uses falsey parsed values as its empty state. Therefore 0, false, "", and null export as null; do not use this option when those values must round-trip distinctly.

For import/export lifecycle, defaults, and target copying, see Importing and Exporting Data.

🔗
❓
<div id="myForm">
    <p>
        <label data-smark>Name:</label>
        <input type="text" name="name" data-smark>
    </p>
    <p>
        <label data-smark>Age:</label>
        <input type="number" name="age" min="0" max="150" data-smark>
    </p>
    <p>
        <label data-smark>Date of Birth:</label>
        <input type="date" name="dob" data-smark>
    </p>
    <p>
        <label data-smark>Metadata (JSON):</label>
        <textarea name="metadata" data-smark='{"format":"json"}'></textarea>
    </p>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
    "value": {
    "name": "Alice",
    "age": "28",
    "dob": "19960315",
    "metadata": {
        "subscribed": true,
        "tier": "premium"
    }
}
});

👉 Number coercion: The Age field is <input type="number">.

  • SmarkForm always exports its value as a JavaScript number, not a string.
  • It also accepts string representations on import — try clicking ⬇️ Export, changing "age": 28 to "age": "28" (quoted) in the JSON playground editor, and clicking ⬆️ Import: the exported result will be "age": 28 (unquoted) again.

👉 Date normalization: The Date of Birth field is <input type="date">.

  • SmarkForm always exports an ISO 8601 string (YYYY-MM-DD).
  • It accepts compact strings (YYYYMMDD) and Date objects on import. Try clicking ⬇️ Export, changing "dob": "1996-03-15" to "dob": "19960315" in the JSON playground editor, and clicking ⬆️ Import — it will be normalised to "1996-03-15" on the next export.

👉 JSON format: The Metadata textarea has {"format":"json"}.

  • On import, an object or array is serialised to JSON text (pretty-printed in textareas for readability).
  • On export, the textarea content is parsed back into a JavaScript value — your saved data contains a real object, not a raw JSON string.
  • Works with any valid JSON: objects, arrays, numbers, booleans, and null.

See also: Data Import and Export, Form Types, List Types