{"record":{"id":"3e923f9aad5c3a7f","repo":"babalae/better-genshin-impact","slug":"format-notes","errorCode":null,"errorMessage":"{format} 曲谱的 notes 必须是字符串","messagePattern":"(.+?) 曲谱的 notes 必须是字符串","errorType":"exception","errorClass":"FormatException","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/GameTask/Music/Service/MusicScoreParser.cs","lineNumber":695,"sourceCode":"        return token == null || token.Type == JTokenType.Null\n            ? fallback\n            : token.ToString();\n    }\n\n    private static double GetDouble(JObject json, string propertyName, double fallback)\n    {\n        var value = GetString(json, propertyName, fallback.ToString(CultureInfo.InvariantCulture));\n        return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)\n               && result > 0\n            ? result\n            : fallback;\n    }\n\n    private static string GetNotesString(JToken notes, MusicScoreFormat format)\n    {\n        return notes.Type == JTokenType.String\n            ? notes.Value<string>() ?? string.Empty\n            : throw new FormatException($\"{format} 曲谱的 notes 必须是字符串\");\n    }\n\n    private static PerformanceScore CreateInvalidScore(string path, string rootFolder, string error)\n    {\n        return new PerformanceScore\n        {\n            FullPath = path,\n            RelativePath = Path.GetRelativePath(rootFolder, path),\n            Name = Path.GetFileNameWithoutExtension(path),\n            Format = Path.GetExtension(path).Equals(\".json\", StringComparison.OrdinalIgnoreCase)\n                ? MusicScoreFormat.YuanQin\n                : MusicScoreFormat.MidiFile,\n            Error = error\n        };\n    }\n\n    [GeneratedRegex(@\"^(?<status>[DU])(?<keys>[A-Z@]+)(?<ticks>\\d+)$\", RegexOptions.CultureInvariant)]\n    private static partial Regex MidiJsonEventRegex();","sourceCodeStart":677,"sourceCodeEnd":713,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/GameTask/Music/Service/MusicScoreParser.cs#L677-L713","documentation":"A FormatException from GetNotesString thrown when the \"notes\" token of a MIDI-JSON or keyboard-format score is not a JSON string. For these two formats the parser expects the entire notes payload to be a single delimited string (not an array/object); a different JTokenType is rejected.","triggerScenarios":"A JSON score with type \"midi\" or \"keyboard\" whose \"notes\" is an array or object (e.g. notes:[...] or notes:{...}) instead of a string. Yuanqin does not hit this path because it uses ParseYuanQinTimeline which expects a JArray.","commonSituations":"Using a yuanqin-style notes array with type set to \"midi\"; exporter that emits notes as an array for a format that expects a string; copy-paste of a notes array into a midi/keyboard score.","solutions":["Set the score \"type\" to match the notes shape: use \"yuanqin\" for an array, or \"midi\"/\"keyboard\" for a string.","If the type is correct, convert the notes value to a single string in the format the parser expects.","Route through ParseAsync to surface InvalidScore.Error with the format name.","Add a type-vs-notes-shape validator before parsing."],"exampleFix":"// before (midi type but array notes)\n{\"type\":\"midi\",\"notes\":[\"DQ:0\"]}\n// after\n{\"type\":\"midi\",\"notes\":\"DQ:0|UQ:480\"}","handlingStrategy":"validation","validationCode":"static bool NotesShapeMatchesType(string type, JToken notes)\n    => type switch\n    {\n        \"yuanqin\" => notes is JArray,\n        \"midi\" or \"keyboard\" => notes.Type == JTokenType.String,\n        _ => false\n    };","typeGuard":"static bool IsNotesString(JToken t) => t.Type == JTokenType.String;","tryCatchPattern":"var score = await parser.ParseAsync(path, root, ct);\nif (!string.IsNullOrEmpty(score.Error)) { Log.Warning(score.Error); continue; }","preventionTips":["Match the \"type\" field to the notes shape (array for yuanqin, string for midi/keyboard).","Validate type-vs-notes shape before parsing.","Use ParseAsync for InvalidScore reporting."],"tags":["music-score","json-parsing","input-validation","type-mismatch"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}