{"record":{"id":"6dd3e2f52faf6d16","repo":"Tencent/APIJSON","slug":"originkey-value-k-key-value","errorCode":null,"errorMessage":"originKey + \":value 中字符 \" + k + \" 不合法！key$:value 中不允许 key 中有连续相同的占位符！","messagePattern":"originKey \\+ \":value 中字符 \" \\+ k \\+ \" 不合法！key\\$:value 中不允许 key 中有连续相同的占位符！","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/Join.java","lineNumber":300,"sourceCode":"\t\t\t\tk = originKey.substring(0, originKey.length() - 2);\n\t\t\t}\n\t\t\telse if (originKey.endsWith(\"<>\")) {\n\t\t\t\tsetRelateType(\"<>\");\n\t\t\t\tk = originKey.substring(0, originKey.length() - 2);\n\t\t\t}\n\t\t\telse if (originKey.endsWith(\"$\")) {  // key%$:\"a\" -> key LIKE '%a%'; key?%$:\"a\" -> key LIKE 'a%'; key_?$:\"a\" -> key LIKE '_a'; key_%$:\"a\" -> key LIKE '_a%'\n\t\t\t\tk = originKey.substring(0, originKey.length() - 1);\n\t\t\t\tchar c = k.isEmpty() ? 0 : k.charAt(k.length() - 1);\n\n\t\t\t\tString t = \"$\";\n\t\t\t\tif (c == '%' || c == '_' || c == '?') {\n\t\t\t\t\tt = c + t;\n\t\t\t\t\tk = k.substring(0, k.length() - 1);\n\n\t\t\t\t\tchar c2 = k.isEmpty() ? 0 : k.charAt(k.length() - 1);\n\t\t\t\t\tif (c2 == '%' || c2 == '_' || c2 == '?') {\n\t\t\t\t\t\tif (c2 == c) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException(originKey + \":value 中字符 \" + k + \" 不合法！key$:value 中不允许 key 中有连续相同的占位符！\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tt = c2 + t;\n\t\t\t\t\t\tk = k.substring(0, k.length() - 1);\n\t\t\t\t\t}\n\t\t\t\t\telse if (c == '?') {\n\t\t\t\t\t\tthrow new IllegalArgumentException(originKey + \":value 中字符 \" + originKey + \" 不合法！key$:value 中不允许只有单独的 '?'，必须和 '%', '_' 之一配合使用 ！\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsetRelateType(t);\n\t\t\t}\n\t\t\telse if (originKey.endsWith(\"~\")) {\n\t\t\t\tboolean ignoreCase = originKey.endsWith(\"*~\");\n\t\t\t\tsetRelateType(ignoreCase ? \"*~\" : \"~\");\n\t\t\t\tk = originKey.substring(0, originKey.length() - (ignoreCase ? 2 : 1));\n\t\t\t}\n\t\t\telse if (originKey.endsWith(\">=\")) {","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/Join.java#L282-L318","documentation":"For LIKE-type join keys ending in '$' (key$:value → key LIKE value), APIJSON lets you prepend one or two of the placeholders % _ ? (e.g. key_%$ → LIKE '_v%', key?%$ → LIKE 'v%'). While peeling the trailing placeholder chars, if two consecutive characters are the SAME placeholder (key%%$, key__$, key??$) it throws, because '%%' / '__' / '??' are redundant or ambiguous and almost certainly a typo.","triggerScenarios":"A join table object contains a key like \"name%%$\":\"a\" (two consecutive %), \"code__$\":\"a\", or \"flag??$\":\"a\". The parser peels one '%', sees c2 == c ('%' again) and throws IllegalArgumentException listing the offending key.","commonSituations":"Developer intends a literal double underscore in a column name and forgets the operator parsing treats trailing _ as placeholder; typo doubling the placeholder; migrating SQL LIKE patterns ('%%' for empty pattern in some dialects) straight into key names.","solutions":["Use each placeholder at most once: \"name_%$\":\"a\" (LIKE '_a%'), \"name?%$\":\"a\" (LIKE 'a%').","If you need consecutive placeholder characters in the actual SQL pattern, put them in the value (e.g. \"name$\":\"%a%%\") rather than the key suffix.","Rename double-underscored column keys so they do not end with the placeholder+operator sequence, or alias the column."],"exampleFix":"// before\n\"User\":{\"name%%$\":\"a\"}  // rejected: consecutive identical placeholders\n// after\n\"User\":{\"name_%$\":\"a\"}  // LIKE '_a%'\n// or put full pattern in value:\n\"User\":{\"name$\":\"%a%\"}","handlingStrategy":"validation","validationCode":"const LIKE_KEY = /^!?[A-Za-z]\\w*(\\?%|_%|\\?_|\\?|_|%)?\\$$/;\nfunction assertLikeJoinKey(k) {\n  if (!LIKE_KEY.test(k)) throw new Error('Bad LIKE join key: ' + k);\n  const m = k.match(/(.)\\1\\$$/);   // detects doubled trailing placeholder\n  if (m) throw new Error('Consecutive identical placeholder ' + m[1] + m[1] + ' in ' + k);\n}","typeGuard":"function isLikeJoinKey(k) { return /^!?[A-Za-z]\\w*([%_?]{0,2})\\$$/.test(k) && !/(.)\\1\\$$/.test(k); }","tryCatchPattern":null,"preventionTips":["Remember each of % _ ? may appear at most once, and never twice consecutively, in a '$' join key.","Put full LIKE patterns in the value, not the key: key$ → LIKE '%value%'.","Add a client-side key linter for join objects mirroring the server grammar."],"tags":["apijson","join","like-operator","request-syntax"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}