flxzt/rnote · error · anyhow::Error

value `stroke_components` is not a JSON array.

Error message

value `stroke_components` is not a JSON array.

What it means

Thrown by the same maj0min5patch9 TryFrom migration when "stroke_components" exists in store_snapshot but is not a JSON array. The migration iterates the value with as_array_mut() to rewrite each stroke, so anything else fails.

Solutions

  1. Change "stroke_components" in store_snapshot to a JSON array (wrap the value in [ ] if it was a single object).
  2. Re-save the file from the app version that produced a valid snapshot, then retry.
  3. Restore from backup.

Example fix

// before
"stroke_components": { "0": {...} }
// after
"stroke_components": [ { ... } ]
Defensive patterns

Strategy: type-guard

Validate before calling

let comps = snap.get("stroke_components")?;
if !comps.is_array() { return Err("stroke_components must be a JSON array".into()); }

Type guard

fn is_valid_stroke_components(snap: &serde_json::Value) -> bool {
    snap.get("stroke_components").map(|c| c.is_array()).unwrap_or(false)
}

Try / catch

match migrate(file) {
    Ok(f) => f,
    Err(e) if e.to_string().contains("not a JSON array") => {
        eprintln!("snapshot schema invalid; restore from backup: {e}");
        return Err(e);
    }
}

Prevention

When it happens

Trigger: Migrating an .rnote file where store_snapshot["stroke_components"] is an object, string, number, or null instead of an array.

Common situations: Schema drift between very old file versions, hand edits, or external tooling that rewrote the snapshot incorrectly.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of flxzt/rnote@bbc5354502 (2026-09-08). Data as JSON: /api/errors/5de19e7039015b8f. Report an issue: GitHub.

Appendix: source

Thrown at crates/rnote-engine/src/fileformats/rnoteformat/maj0min5patch9.rs:28

pub(crate) struct RnoteFileMaj0Min5Patch9 {
    /// The document.
    #[serde(rename = "document", alias = "sheet")]
    pub(crate) document: ijson::IValue,
    /// The snapshot of the store.
    #[serde(rename = "store_snapshot")]
    pub(crate) store_snapshot: ijson::IValue,
}

impl TryFrom<RnoteFileMaj0Min5Patch8> for RnoteFileMaj0Min5Patch9 {
    type Error = anyhow::Error;

    fn try_from(mut file: RnoteFileMaj0Min5Patch8) -> Result<RnoteFileMaj0Min5Patch9, Self::Error> {
        let stroke_components = file
            .store_snapshot
            .get_mut("stroke_components")
            .ok_or_else(|| anyhow!("no value `stroke_components` in `store_snapshot`"))?
            .as_array_mut()
            .ok_or_else(|| anyhow!("value `stroke_components` is not a JSON array."))?;

        for value in stroke_components {
            let stroke = value
                .as_object_mut()
                .ok_or_else(|| anyhow!("value in `stroke_components` array is not a JSON Object."))?
                .get_mut("value")
                .ok_or_else(|| {
                    anyhow!("no value `value` in JSON object of `stroke_components` array.")
                })?;

            if stroke.is_null() {
                continue;
            }

            if let Some(brushstroke) = stroke
                .as_object_mut()
                .ok_or_else(|| anyhow!("stroke value is not a JSON Object."))?
                .get_mut("brushstroke")

View on GitHub (pinned to bbc5354502)