{"record":{"id":"46e79e3b43e15463","repo":"languagetool-org/languagetool","slug":"failed-to-parse-line","errorCode":null,"errorMessage":"Failed to parse line ","messagePattern":"Failed to parse line ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"languagetool-dev/src/main/java/org/languagetool/dev/diff/LightRuleMatchParser.java","lineNumber":72,"sourceCode":"    ObjectMapper mapper = new ObjectMapper();\n    List<LightRuleMatch> ruleMatches = new ArrayList<>();\n    Set<String> buildDates = new HashSet<>();\n    int lineCount = 1;\n    try (Scanner scanner = new Scanner(inputFile)) {\n      while (scanner.hasNextLine()) {\n        String line = scanner.nextLine();\n        JsonNode node = mapper.readTree(line);\n        JsonNode matches = node.get(\"matches\");\n        JsonNode software = node.get(\"software\");\n        String buildDate = software != null ? software.get(\"buildDate\").asText() : \"unknown\";\n        buildDates.add(buildDate);\n        for (JsonNode match : matches) {\n          ruleMatches.add(nodeToLightMatch(node.get(\"title\").asText(), match));\n        }\n        lineCount++;\n      }\n    } catch (Exception e) {\n      throw new RuntimeException(\"Failed to parse line \" + lineCount + \" of \" + inputFile, e);\n    }\n    return new JsonParseResult(ruleMatches, buildDates);\n  }\n\n  @NotNull\n  private LightRuleMatch nodeToLightMatch(String title, JsonNode match) {\n    int offset = match.get(\"offset\").asInt();\n    JsonNode rule = match.get(\"rule\");\n    String ruleId = rule.get(\"id\").asText();\n    String fullRuleId = rule.get(\"subId\") != null ? ruleId + \"[\" + rule.get(\"subId\").asText() + \"]\" : ruleId;\n    String message = match.get(\"message\").asText();\n    String category = rule.get(\"category\") != null ? rule.get(\"category\").get(\"name\").asText() : \"(unknown)\";\n    //TODO: Maybe just works for xml rules\n    boolean isPremium = rule.get(\"isPremium\") != null && rule.get(\"isPremium\").asBoolean(false);\n    int contextOffset = match.get(\"context\").get(\"offset\").asInt();\n    int contextLength = match.get(\"context\").get(\"length\").asInt();\n    String context = match.get(\"context\").get(\"text\").asText();\n    int maxEnd = Math.min(contextOffset + contextLength, context.length());","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-dev/src/main/java/org/languagetool/dev/diff/LightRuleMatchParser.java#L54-L90","documentation":"LightRuleMatchParser.parseAggregatedJson wraps any exception raised while parsing a line of the aggregated JSON input file into a RuntimeException. The message includes the line number (lineCount) and the input file path so the offending JSON line can be located; the original exception is attached as the cause (e.g. Jackson mapping or NPE errors).","triggerScenarios":"Calling parseOutput/parseAggregatedJson on an aggregated JSON file where a line is malformed JSON, is missing expected fields (e.g. 'matches', rule match 'title' node), or has an unexpected structure so nodeToLightMatch throws.","commonSituations":"Feed files produced by an older LanguageTool version no longer matching the expected aggregated JSON schema; hand-edited or truncated diff input files; running the diff tooling against output from a differently configured server.","solutions":["Open the input file at the reported line number and fix or remove the malformed JSON line","Regenerate the aggregated input file with the same LanguageTool version the parser expects","Validate each line is complete JSON (e.g. with jq) before feeding it to the parser","Inspect the cause chain (e.getCause()) to see whether it is a JSON syntax error or a missing-field NPE"],"exampleFix":"// before: feeding possibly blank/truncated lines\nresult = parser.parseOutput(new File(\"agg.jsonl\"));\n// after: pre-validate lines\nfor (String line : Files.readAllLines(Path.of(\"agg.jsonl\"))) {\n  if (line.isBlank()) continue;\n  new JsonParser().parse(line); // throws early with clear context\n}\nresult = parser.parseOutput(new File(\"agg.jsonl\"));","handlingStrategy":"validation","validationCode":"// validate each JSONL line before parsing\nfor (String line : Files.readAllLines(Path.of(inputFile))) {\n  if (line.isBlank()) throw new IllegalStateException(\"blank line in \" + inputFile);\n  JsonNode n = new ObjectMapper().readTree(line);\n  if (!n.has(\"matches\")) throw new IllegalStateException(\"line missing 'matches': \" + line);\n}","typeGuard":"static boolean isValidAggLine(String line) {\n  try { return new ObjectMapper().readTree(line).has(\"matches\"); }\n  catch (Exception e) { return false; }\n}","tryCatchPattern":"try {\n  result = parser.parseOutput(inputFile);\n} catch (RuntimeException e) {\n  // message contains 'Failed to parse line N of FILE'\n  log.error(\"aggregated JSON invalid: \" + e.getMessage(), e.getCause());\n  throw new IllegalArgumentException(\"fix input file at reported line\", e);\n}","preventionTips":["Keep line-oriented JSON files machine-generated, never hand-edited","Validate with jq or a schema checker before feeding to the diff tooling","Pin the same LanguageTool version for producing and parsing diff files"],"tags":["java","json-parsing","runtime-exception"],"backgroundTag":"json-parse-error","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}