flxzt/rnote · error

brushstroke is not a JSON object.

Error message

brushstroke is not a JSON object.

What it means

During the maj0min5patch8 -> maj0min5patch9 migration, when a stroke value contains a `brushstroke` key, that value must be a JSON object so the migration can extract and upgrade its `path`. This error is thrown when the `brushstroke` value is a non-object JSON value.

Solutions

  1. Unzip the .rnote file and confirm `brushstroke` is a JSON object with a `path` key.
  2. Restore the document from a backup or autosave copy.
  3. Re-save the file with a matching rnote version so brushstroke is serialized correctly.
  4. If editing manually, restructure the entry as `{ "brushstroke": { "path": [...], ... } }`.

Example fix

// before
{ "value": { "brushstroke": "corrupted" } }
// after
{ "value": { "brushstroke": { "path": [] } } }
Defensive patterns

Strategy: type-guard

Validate before calling

// validate before migration
fn brushstrokes_are_objects(doc: &serde_json::Value) -> bool {
    doc["store_snapshot"]["stroke_components"]
        .as_array()
        .map(|arr| arr.iter().all(|v|
            v["value"].get("brushstroke").map_or(true, |b| b.is_object())))
        .unwrap_or(false)
}

Type guard

fn brushstroke_is_object(entry: &serde_json::Value) -> bool {
    entry["value"]["brushstroke"]
        .as_object()
        .map(|b| b.contains_key("path"))
        .unwrap_or(false)
}

Try / catch

match RnoteFileMaj0Min5Patch9::try_from(file) {
    Ok(migrated) => /* use migrated */,
    Err(e) if e.to_string().contains("brushstroke is not a JSON object") => {
        // drop or rebuild the malformed brushstroke entry
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `RnoteFileMaj0Min5Patch9::try_from` on a file where `stroke_components[i].value.brushstroke` is a string, number, array, or boolean instead of an object.

Common situations: Corrupted or hand-edited .rnote files; files written by experimental/forked rnote builds that serialized brushstroke differently.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

                .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")
            {
                let brushstroke = brushstroke
                    .as_object_mut()
                    .ok_or_else(|| anyhow!("brushstroke is not a JSON object."))?;
                let path = ijson::from_value::<PenPathMaj0Min5Patch8>(
                    &brushstroke
                        .remove("path")
                        .ok_or_else(|| anyhow!("brushstroke has no value `path`."))?,
                )?;
                let mut path_upgraded = ijson::IObject::new();
                let mut seg_iter = path.inner().into_iter().peekable();

                if let Some(start) = seg_iter.peek() {
                    let start = match start {
                        SegmentMaj0Min5Patch8::Dot { element } => element,
                        SegmentMaj0Min5Patch8::Line { start, .. } => start,
                        SegmentMaj0Min5Patch8::QuadBez { start, .. } => start,
                        SegmentMaj0Min5Patch8::CubBez { start, .. } => start,
                    };
                    path_upgraded.insert(String::from("start"), ijson::to_value(start)?);
                    let mut segments_upgraded = ijson::IArray::new();

View on GitHub (pinned to bbc5354502)