{"record":{"id":"7bbefa1bf68d0169","repo":"Tencent/APIJSON","slug":"join-key-app-join","errorCode":null,"errorMessage":"@join:'{}' 对应的 {} 不是合法的数组 key[] ！@ APP JOIN 最多允许跨 1 层，只能是子数组，且数组对象中不能有 join: value 键值对！","messagePattern":"@join:'(.+?)' 对应的 (.+?) 不是合法的数组 key\\[\\] ！@ APP JOIN 最多允许跨 1 层，只能是子数组，且数组对象中不能有 join: value 键值对！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractParser.java","lineNumber":1630,"sourceCode":"\t\t\t}\n\n\t\t\tint index = path == null ? -1 : path.indexOf(\"/\");\n\t\t\tif (index < 0) {\n\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":value 中 value 值 \" + path + \" 不合法！\"\n\t\t\t\t\t\t+ \"必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,... 或 { '&/Table0':{}, '</Table1/key1':{},... } 这种形式！\");\n\t\t\t}\n\t\t\tString joinType = path.substring(0, index); //& | ! < > ( ) <> () *\n\t\t\t//\t\t\tif (StringUtil.isEmpty(joinType, true)) {\n\t\t\t//\t\t\t\tjoinType = \"|\"; // FULL JOIN\n\t\t\t//\t\t\t}\n\t\t\tpath = path.substring(index + 1);\n\n\t\t\tindex = path.lastIndexOf(\"/\");\n\t\t\tString tableKey = index < 0 ? path : path.substring(0, index); // User:owner\n\t\t\tint index2 = tableKey.lastIndexOf(\"/\");\n\t\t\tString arrKey = index2 < 0 ? null : tableKey.substring(0, index2);\n\t\t\tif (arrKey != null && isArrayKey(arrKey) == false) {\n\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":'\" + e.getKey() + \"' 对应的 \" + arrKey + \" 不是合法的数组 key[] ！\" +\n\t\t\t\t\t\t\"@ APP JOIN 最多允许跨 1 层，只能是子数组，且数组对象中不能有 join: value 键值对！\");\n\t\t\t}\n\n\t\t\ttableKey = index2 < 0 ? tableKey : tableKey.substring(index2+1);\n\n\t\t\tapijson.orm.Entry<String, String> entry = Pair.parseEntry(tableKey, true);\n\t\t\tString table = entry.getKey(); // User\n\t\t\tif (StringUtil.isName(table) == false) {\n\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":value 中 value 的 Table 值 \" + table + \" 不合法！\"\n\t\t\t\t\t\t+ \"必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,... 或 { '&/Table0':{}, '</Table1/key1':{},... } 这种格式！\"\n\t\t\t\t\t\t+ \"且 Table 必须满足大写字母开头的表对象英文单词 key 格式！\");\n\t\t\t}\n\n\t\t\tString alias = entry.getValue(); // owner\n\t\t\tif (StringUtil.isNotEmpty(alias, true) && StringUtil.isName(alias) == false) {\n\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":value 中 value 的 alias 值 \" + alias + \" 不合法！\"\n\t\t\t\t\t\t+ \"必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,... 或 { '&/Table0':{}, '</Table1/key1':{},... } 这种格式！\"\n\t\t\t\t\t\t+ \"且 Table:alias 的 alias 必须满足英文单词变量名格式！\");","sourceCodeStart":1612,"sourceCodeEnd":1648,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1612-L1648","documentation":"When a join path contains a middle segment between the type sigil and the table (arrKey, e.g. 'list[]' in '@/list[]/User'), that segment is validated with isArrayKey(): it must literally end with '[]'. A non-array middle segment throws IllegalArgumentException; the message mentions @ APP JOIN because spanning into a sub-array is the @-join feature, limited to one level, with no join key allowed inside the sub-array object.","triggerScenarios":"\"@join\":\"@/a[]/b[]/User\" (two array levels), or '@/someObj/User/id' where someObj is a plain object not ending in []. Any join whose path has two slashes hits the arrKey check.","commonSituations":"Deep nested comment trees with app-join on both levels; using APP JOIN where SQL JOIN works fine; forgetting that the target must be a key[] array object inside the current request.","solutions":["Use SQL joins (& < > etc.) instead of @ APP JOIN for cross-level linking — they are not depth-limited this way","Flatten the request so the app-joined table sits directly inside one array level: '@/list[]/User'","Ensure the middle segment literally ends with '[]' (isArrayKey check)","Do not put a join key inside the sub-array object (see companion error)"],"exampleFix":"// before\n{\"@join\":\"@/a[]/b[]/User\",\"a[]\":{\"b[]\":{\"User\":{}}}}\n// after\n{\"@join\":\"&/User/id\",\"a[]\":{\"b[]\":{\"User\":{\"id@\":\"...\"}}}}","handlingStrategy":"validation","validationCode":"String middle = pathBetweenSigilAndTable(p);\nif (middle != null && !middle.endsWith(\"[]\")) throw new IllegalArgumentException(\"@ join middle key must be array key[]\");","typeGuard":"boolean isArrayKey(String k) { return k != null && k.endsWith(\"[]\"); }","tryCatchPattern":null,"preventionTips":["Use @ APP JOIN only one level deep; switch to & or < SQL joins otherwise","Ensure the referenced middle key literally ends with []","Never place join keys inside the app-joined sub-array object"],"tags":["apijson","app-join","depth-limit","join"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}