flxzt/rnote · error · anyhow::Error

engine snapshot is not a JSON object.

Error message

engine snapshot is not a JSON object.

What it means

Thrown when converting an RnoteFileMaj0Min13 to RnoteFileMaj0Min15 if `engine_snapshot` is not a JSON object (e.g. it is null, an array, or a scalar). The migration needs to mutate keys inside the snapshot object, so it must first downcast it to a mutable IObject.

Solutions

  1. Verify the .rnote file's engine_snapshot is a JSON object (unzip and inspect)
  2. If the file is corrupt, recover content by re-importing or re-saving from a working rnote build
  3. In code, check `value.engine_snapshot.is_object()` before conversion and handle null snapshots with a default snapshot

Example fix

// before
let engine_snapshot = value
    .engine_snapshot
    .as_object_mut()
    .ok_or(anyhow!("engine snapshot is not a JSON object."))?;
// after
if value.engine_snapshot.is_null() {
    value.engine_snapshot = serde_json::json!({ "stroke_components": [] }).into();
}
let engine_snapshot = value
    .engine_snapshot
    .as_object_mut()
    .ok_or(anyhow!("engine snapshot is not a JSON object."))?;
Defensive patterns

Strategy: type-guard

Validate before calling

if !value.engine_snapshot.is_object() {
    return Err(anyhow!("refusing migration: engine snapshot is not a JSON object"));
}

Type guard

fn snapshot_is_object(v: &ijson::IValue) -> bool {
    v.as_object().is_some()
}

Try / catch

// convert and map the failure to a user-facing message
let file = RnoteFileMaj0Min15::try_from(file_maj0min13)
    .context("This .rnote file's engine snapshot is invalid; the file may be corrupted or from an unsupported version.")?;

Prevention

When it happens

Trigger: Calling TryFrom on an RnoteFileMaj0Min13 whose `engine_snapshot` field deserialized to a non-object JSON value — typically a null snapshot from a corrupt/older file, or a struct constructed manually with wrong data.

Common situations: Opening a damaged .rnote file where the engine snapshot failed to parse; hand-crafted test fixtures with engine_snapshot set to null; upstream format changes that moved the snapshot.

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/022e2ff8c851b53a. Report an issue: GitHub.

Appendix: source

Thrown at crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs:21

use anyhow::anyhow;
use ijson::IValue;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RnoteFileMaj0Min15 {
    /// A snapshot of the engine.
    #[serde(rename = "engine_snapshot")]
    pub engine_snapshot: ijson::IValue,
}

impl TryFrom<RnoteFileMaj0Min13> for RnoteFileMaj0Min15 {
    type Error = anyhow::Error;

    fn try_from(mut value: RnoteFileMaj0Min13) -> Result<Self, Self::Error> {
        let engine_snapshot = value
            .engine_snapshot
            .as_object_mut()
            .ok_or(anyhow!("engine snapshot is not a JSON object."))?;

        for comp in engine_snapshot
            .get_mut("stroke_components")
            .ok_or(anyhow!(
                "engine snapshot does not contain 'stroke_components'."
            ))?
            .as_array_mut()
            .ok_or(anyhow!("stroke components is not a JSON array."))?
            .iter_mut()
        {
            let value = comp
                .as_object_mut()
                .ok_or(anyhow!("stroke component is not a JSON object."))?
                .get_mut("value")
                .ok_or(anyhow!("stroke component does not contain 'value'."))?;
            if value.is_null() {
                continue;
            }

View on GitHub (pinned to bbc5354502)