{"record":{"id":"3c3abf3ec92e7169","repo":"babalae/better-genshin-impact","slug":"yuanqin-note","errorCode":null,"errorMessage":"yuanqin 音符对象缺少 note 字段","messagePattern":"yuanqin 音符对象缺少 note 字段","errorType":"exception","errorClass":"FormatException","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/GameTask/Music/Service/MusicScoreParser.cs","lineNumber":351,"sourceCode":"        }\n\n        return tokens;\n    }\n\n    private static List<YuanQinToken> ParseYuanQinTokens(JArray notes)\n    {\n        var tokens = new List<YuanQinToken>(notes.Count);\n        foreach (var item in notes)\n        {\n            if (item is not JObject note)\n            {\n                throw new FormatException(\"yuanqin 音符数组中包含非对象元素\");\n            }\n\n            var keys = GetString(note, \"note\", string.Empty);\n            if (string.IsNullOrWhiteSpace(keys))\n            {\n                throw new FormatException(\"yuanqin 音符对象缺少 note 字段\");\n            }\n\n            var special = GetString(note, \"spl\", \"none\");\n            if (special is \"^\" or \"&\")\n            {\n                tokens.Add(new YuanQinToken(keys, 0, special));\n                continue;\n            }\n\n            if (!double.TryParse(\n                    note[\"type\"]?.ToString(),\n                    NumberStyles.Float,\n                    CultureInfo.InvariantCulture,\n                    out var denominator))\n            {\n                throw new FormatException($\"无法解析音符 {keys} 的时值\");\n            }\n","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/GameTask/Music/Service/MusicScoreParser.cs#L333-L369","documentation":"A FormatException raised inside ParseYuanQinTokens while iterating the \"notes\" array of a yuanqin-format AutoYuanQin JSON score. Each element must be an object carrying a non-empty \"note\" field (the key letters). GetString returns an empty fallback when the field is absent or null, and IsNullOrWhiteSpace then trips this throw. It is a contract violation in the score file, not a transient runtime fault.","triggerScenarios":"Parsing a JSON score whose top-level \"type\" resolves to \"yuanqin\" where the \"notes\" JArray contains an element that is a JObject but has no \"note\" key, or whose \"note\" value is an empty/whitespace string.","commonSituations":"Hand-edited or converter-generated yuanqin files where a note object was dropped to {}; scores exported by a tool that renamed the key (e.g. \"key\"/\"k\" instead of \"note\"); a truncated/copy-paste file missing fields.","solutions":["Open the offending .json file and confirm every object in the notes array has a non-empty \"note\" field (e.g. {\"note\":\"Q\",\"type\":4}).","If you generated the file with a converter, regenerate it and verify the converter emits the \"note\" key, not a synonym.","At the public API surface (ParseAsync) this throw is already swallowed and converted into a PerformanceScore with Error set, so surface score.Error to the user instead of letting the raw exception propagate.","Add a JSON-schema pre-check on yuanqin files before parsing so malformed notes are reported with the offending index."],"exampleFix":"// before: note object missing the field\n{\"type\":\"yuanqin\",\"bpm\":120,\"notes\":[{\"type\":4}]}\n// after: every note carries \"note\"\n{\"type\":\"yuanqin\",\"bpm\":120,\"notes\":[{\"note\":\"Q\",\"type\":4}]}","handlingStrategy":"try-catch","validationCode":"// Validate a yuanqin notes array before parsing\nstatic bool HasValidYuanQinNotes(JToken notes)\n{\n    if (notes is not JArray arr) return false;\n    foreach (var item in arr)\n    {\n        if (item is not JObject o) return false;\n        var n = o[\"note\"]?.ToString();\n        if (string.IsNullOrWhiteSpace(n)) return false;\n    }\n    return true;\n}","typeGuard":"static bool IsYuanQinNoteObject(JToken t) => t is JObject o && !string.IsNullOrWhiteSpace(o[\"note\"]?.ToString());","tryCatchPattern":"// ParseAsync already converts this to an InvalidScore; check the result\nvar score = await parser.ParseAsync(path, root, ct);\nif (!string.IsNullOrEmpty(score.Error))\n{\n    Log.Warning(\"跳过无效曲谱 {Path}: {Error}\", path, score.Error);\n    continue;\n}","preventionTips":["Always go through the public ParseAsync; it swallows parse exceptions into score.Error.","Lint score files with a JSON schema before importing.","Report the failing note index in your own validator for faster fixes."],"tags":["music-score","json-parsing","input-validation","yuanqin"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}