{"record":{"id":"022e2ff8c851b53a","repo":"flxzt/rnote","slug":"engine-snapshot-is-not-a-json-object-022e2f","errorCode":null,"errorMessage":"engine snapshot is not a JSON object.","messagePattern":"engine snapshot is not a JSON object\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs","lineNumber":21,"sourceCode":"use anyhow::anyhow;\nuse ijson::IValue;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct RnoteFileMaj0Min15 {\n    /// A snapshot of the engine.\n    #[serde(rename = \"engine_snapshot\")]\n    pub engine_snapshot: ijson::IValue,\n}\n\nimpl TryFrom<RnoteFileMaj0Min13> for RnoteFileMaj0Min15 {\n    type Error = anyhow::Error;\n\n    fn try_from(mut value: RnoteFileMaj0Min13) -> Result<Self, Self::Error> {\n        let engine_snapshot = value\n            .engine_snapshot\n            .as_object_mut()\n            .ok_or(anyhow!(\"engine snapshot is not a JSON object.\"))?;\n\n        for comp in engine_snapshot\n            .get_mut(\"stroke_components\")\n            .ok_or(anyhow!(\n                \"engine snapshot does not contain 'stroke_components'.\"\n            ))?\n            .as_array_mut()\n            .ok_or(anyhow!(\"stroke components is not a JSON array.\"))?\n            .iter_mut()\n        {\n            let value = comp\n                .as_object_mut()\n                .ok_or(anyhow!(\"stroke component is not a JSON object.\"))?\n                .get_mut(\"value\")\n                .ok_or(anyhow!(\"stroke component does not contain 'value'.\"))?;\n            if value.is_null() {\n                continue;\n            }","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-engine/src/fileformats/rnoteformat/maj0min15.rs#L3-L39","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the .rnote file's engine_snapshot is a JSON object (unzip and inspect)","If the file is corrupt, recover content by re-importing or re-saving from a working rnote build","In code, check `value.engine_snapshot.is_object()` before conversion and handle null snapshots with a default snapshot"],"exampleFix":"// before\nlet engine_snapshot = value\n    .engine_snapshot\n    .as_object_mut()\n    .ok_or(anyhow!(\"engine snapshot is not a JSON object.\"))?;\n// after\nif value.engine_snapshot.is_null() {\n    value.engine_snapshot = serde_json::json!({ \"stroke_components\": [] }).into();\n}\nlet engine_snapshot = value\n    .engine_snapshot\n    .as_object_mut()\n    .ok_or(anyhow!(\"engine snapshot is not a JSON object.\"))?;","handlingStrategy":"type-guard","validationCode":"if !value.engine_snapshot.is_object() {\n    return Err(anyhow!(\"refusing migration: engine snapshot is not a JSON object\"));\n}","typeGuard":"fn snapshot_is_object(v: &ijson::IValue) -> bool {\n    v.as_object().is_some()\n}","tryCatchPattern":"// convert and map the failure to a user-facing message\nlet file = RnoteFileMaj0Min15::try_from(file_maj0min13)\n    .context(\"This .rnote file's engine snapshot is invalid; the file may be corrupted or from an unsupported version.\")?;","preventionTips":["Check `engine_snapshot.is_object()` before conversion","Treat null snapshots as empty snapshots in your loader","Validate .rnote archives after download/transfer (checksums)"],"tags":["json","type-mismatch","migration","rust"],"backgroundTag":"incompatible-source-type","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}