{"record":{"id":"d7193af0b31288aa","repo":"Tencent/APIJSON","slug":"join-e-getkey-key-table0-tab","errorCode":null,"errorMessage":"join:${e.getKey()} 中 ${key} 不合法！必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,... 或 { '&/Table0':{}, '</Table1/key1':{},... } 这种格式！且 Table:alias 的 alias 必须满足英文单词变量名格式！","messagePattern":"join:(.+?) 中 (.+?) 不合法！必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,\\.\\.\\. 或 (.+?), '</Table1/key1':(.+?),\\.\\.\\. \\} 这种格式！且 Table:alias 的 alias 必须满足英文单词变量名格式！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractParser.java","lineNumber":1686,"sourceCode":"\t\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":'\" + e.getKey() + \"' 对应的 \" + arrKey + \":{ join: value } 中 value 不合法！\" +\n\t\t\t\t\t\t\t\"@ APP JOIN 最多允许跨 1 层，只能是子数组，且数组对象中不能有 join: value 键值对！\");\n\t\t\t\t}\n\n\t\t\t\tInteger subPage = getInteger(parentPathObj, apijson.JSONRequest.KEY_PAGE);\n\t\t\t\tif (subPage != null && subPage != 0) {\n\t\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":'\" + e.getKey() + \"' 对应的 \" + arrKey + \":{ page: value } 中 value 不合法！\" +\n\t\t\t\t\t\t\t\"@ APP JOIN 最多允许跨 1 层，只能是子数组，且数组对象中 page 值只能为 null 或 0 ！\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tboolean isAppJoin = \"@\".equals(joinType);\n\n\t\t\tM refObj = JSON.createJSONObject();\n\n\t\t\tString key = index < 0 ? null : path.substring(index + 1); // id@\n\t\t\tif (key != null) {  // 指定某个 key 为 JOIN ON 条件\n\t\t\t\tif (key.indexOf(\"@\") != key.length() - 1) {\n\t\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":\" + e.getKey() + \" 中 \" + key + \" 不合法！\"\n\t\t\t\t\t\t\t+ \"必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,... 或 { '&/Table0':{}, '</Table1/key1':{},... } 这种格式！\"\n\t\t\t\t\t\t\t+ \"且 Table:alias 的 alias 必须满足英文单词变量名格式！\");\n\t\t\t\t}\n\n\t\t\t\tif (tableObj.get(key) instanceof String == false) {\n\t\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":\" + e.getKey() + \"' 对应的 \"\n            \t\t\t+ tableKey + \":{ \" + key + \": value } 中 value 类型不合法！必须为同层级引用赋值路径 String！\");\n\t\t\t\t}\n\n\t\t\t\tif (isAppJoin && StringUtil.isName(key.substring(0, key.length() - 1)) == false) {\n\t\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":'\" + e.getKey() + \"' 中 \" + key + \" 不合法 ！\" +\n\t\t\t\t\t\t\t\"@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接！\");\n\t\t\t\t}\n\n\t\t\t\trefObj.put(key, getString(tableObj, key));\n\t\t\t}\n\n","sourceCodeStart":1668,"sourceCodeEnd":1704,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1668-L1704","documentation":"When a join path specifies an explicit ON key (the segment after the last '/', e.g. '@/Comment/toId@'), that key must be a reference-assignment key ending with '@'. This IllegalArgumentException fires when the trailing segment does not end with '@' (indexOf('@') != length-1), i.e. it is not a reference-assignment key at all.","triggerScenarios":"join values like '@/Comment/toId', '</Moment/id', or '&/User:owner/userId@:' where the final path segment lacks the trailing '@'. The parser takes path.substring(index+1) as the ON key and requires it to look like 'key@'.","commonSituations":"Writing the join ON column name in SQL style ('toId') instead of APIJSON reference style ('toId@'); renaming keys during a refactor and dropping the '@' suffix; copying examples from docs that show the plain column name.","solutions":["Append '@' to the last path segment: '@/Comment/toId' -> '@/Comment/toId@'","Ensure the referenced key exists inside the joined table object as a String whose value is the reference path (e.g. Comment: { 'toId@': '/User/id' })"],"exampleFix":"// before\n{ 'join': '@/Comment/toId', 'Comment': { 'toId@': '/User/id' } }\n// after\n{ 'join': '@/Comment/toId@', 'Comment': { 'toId@': '/User/id' } }","handlingStrategy":"validation","validationCode":"function checkJoinPaths(joinValue) {\n  const paths = typeof joinValue === 'string' ? [joinValue] : Object.keys(joinValue);\n  const bad = paths.filter(p => {\n    const rest = p.substring(p.indexOf('/') + 1);\n    const seg = rest.substring(rest.lastIndexOf('/') + 1);\n    return seg.length > 0 && !seg.endsWith('@');\n  });\n  return bad.length ? `join ON key must end with '@': ${bad.join(', ')}` : null;\n}","typeGuard":"const isJoinOnKey = (seg) => /^[A-Za-z_][A-Za-z0-9_]*@$/.test(seg);","tryCatchPattern":"catch IllegalArgumentException containing '必须为 &/Table0' when the message names your join key -> append '@' to the last path segment and retry once.","preventionTips":["Always name join ON keys with the trailing '@' ('toId@', never 'toId')","Centralize join-path building in one helper that enforces the ?/Table/key@ shape"],"tags":["apijson","join","reference-assignment","validation"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}