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
- Unzip the .rnote file and confirm `brushstroke` is a JSON object with a `path` key.
- Restore the document from a backup or autosave copy.
- Re-save the file with a matching rnote version so brushstroke is serialized correctly.
- 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
- Ensure brushstroke payloads are always serialized as objects containing `path`.
- Do not let third-party tools rewrite stroke JSON without round-trip validation.
- Validate the file with a schema check before attempting format migration.
- Keep an untouched original copy before any manual repair.
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
- value is not a JSON object.
- shapestroke is not a JSON object.
- shape is not a JSON object.
- vectorimage is not a JSON object.
- stroke value is not a JSON Object.
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)