{"record":{"id":"ebdc788f4755d7df","repo":"babalae/better-genshin-impact","slug":"failed-to-deserialize-macro","errorCode":null,"errorMessage":"Failed to deserialize macro","messagePattern":"Failed to deserialize macro","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/Core/Recorder/KeyMouseMacroPlayer.cs","lineNumber":31,"sourceCode":"using System.Threading.Tasks;\nusing System.Windows.Forms;\nusing Fischless.WindowsInput;\nusing Vanara.PInvoke;\nusing Wpf.Ui.Violeta.Controls;\n\nnamespace BetterGenshinImpact.Core.Recorder;\n\npublic class KeyMouseMacroPlayer\n{\n    public static async Task PlayMacro(string macro, CancellationToken ct, bool withDelay = true)\n    {\n        if (!TaskContext.Instance().IsInitialized)\n        {\n            Toast.Warning(\"请先在启动页，启动截图器再使用本功能\");\n            return;\n        }\n\n        var script = JsonSerializer.Deserialize<KeyMouseScript>(macro, KeyMouseRecorder.JsonOptions) ?? throw new Exception(\"Failed to deserialize macro\");\n        script.Adapt(TaskContext.Instance().SystemInfo.CaptureAreaRect, TaskContext.Instance().DpiScale);\n        SystemControl.ActivateWindow();\n\n        if (withDelay)\n        {\n            for (var i = 3; i >= 1; i--)\n            {\n                TaskControl.Logger.LogInformation(\"{Sec}秒后进行重放...\", i);\n                await Task.Delay(1000, ct);\n            }\n\n            TaskControl.Logger.LogInformation(\"开始重放\");\n        }\n\n        await PlayMacro(script.MacroEvents, ct);\n    }\n\n    public static async Task PlayMacro(List<MacroEvent> macroEvents, CancellationToken ct)","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/Core/Recorder/KeyMouseMacroPlayer.cs#L13-L49","documentation":"Thrown by KeyMouseMacroPlayer.PlayMacro at line 31 when JsonSerializer.Deserialize<KeyMouseScript>(macro, KeyMouseRecorder.JsonOptions) yields null. Note the `?? throw` only fires when deserialization succeeds but returns a null reference (input JSON is the literal \"null\"); malformed or empty JSON throws JsonException BEFORE reaching this line. KeyMouseRecorder.JsonOptions uses camelCase naming, allows comments and trailing commas, so structurally-permissive but schema-mismatched input still deserializes to a default/null object rather than throwing.","triggerScenarios":"Calling PlayMacro(macro, ct) where `macro` is the string \"null\", or where a recorded macro file is empty/contains only \"null\". Passing a non-KeyMouseScript-shaped JSON object whose root deserializes to null. Reading a corrupt or truncated .json macro file whose saved content became \"null\".","commonSituations":"A macro .json file was corrupted by an interrupted write (power loss, crash mid-save), leaving it empty or containing \"null\". The user hand-edited the JSON and removed the macroEvents/info fields. A schema/version change in KeyMouseScript made old files deserialize to null. Passing the wrong file's contents (e.g. a config file) into PlayMacro.","solutions":["Validate the macro string before calling PlayMacro: reject null, empty, whitespace, and the literal \"null\".","Catch JsonException separately around the Deserialize call to surface real parse errors with line/byte position.","If the macro file is corrupt, re-record it with the built-in KeyMouseRecorder and overwrite the file.","Log the file path and first 200 chars of the macro string when this fires to identify which file is bad."],"exampleFix":"// before\nvar script = JsonSerializer.Deserialize<KeyMouseScript>(macro, KeyMouseRecorder.JsonOptions)\n    ?? throw new Exception(\"Failed to deserialize macro\");\n\n// after\nif (string.IsNullOrWhiteSpace(macro) || macro.Trim() == \"null\")\n{\n    throw new InvalidOperationException(\"Macro content is empty or null; the macro file may be corrupt. Re-record it.\");\n}\nKeyMouseScript script;\ntry\n{\n    script = JsonSerializer.Deserialize<KeyMouseScript>(macro, KeyMouseRecorder.JsonOptions)\n        ?? throw new InvalidOperationException(\"Failed to deserialize macro: result was null. File may be corrupt or schema-mismatched.\");\n}\ncatch (JsonException ex)\n{\n    throw new InvalidOperationException($\"Failed to parse macro JSON at {ex.Path}: {ex.Message}\", ex);\n}","handlingStrategy":"validation","validationCode":"// C# caller guard before PlayMacro\nif (string.IsNullOrWhiteSpace(macro) || macro.Trim() == \"null\")\n{\n    throw new InvalidOperationException(\"Macro content is empty/null; the macro file may be corrupt. Re-record it.\");\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    var script = JsonSerializer.Deserialize<KeyMouseScript>(macro, KeyMouseRecorder.JsonOptions);\n    if (script is null) throw new InvalidOperationException(\"Macro deserialized to null; file may be corrupt.\");\n}\ncatch (JsonException ex)\n{\n    // Surface the real parse error (path/position) instead of the generic message\n    logger.LogError(ex, \"Macro JSON parse failed at {Path}\", ex.Path);\n    throw;\n}","preventionTips":["Validate macro files after writing by reading them back and deserializing once before storing.","Treat the literal JSON string \"null\" as a corrupt-file signal and prompt re-record.","Catch JsonException separately so a parse error is not misreported as 'deserialized to null'.","Wrap file reads in a sanity check: non-empty, starts with '{', parseable."],"tags":["json","deserialization","macro","recorder","system-text-json"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}