{"record":{"id":"3ac361cce404dc10","repo":"Tencent/APIJSON","slug":"join-value-value-table0-table1","errorCode":null,"errorMessage":"@join:value 中 value 值 {} 不合法！必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,... 或 { '&/Table0':{}, '</Table1/key1':{},... } 这种形式！","messagePattern":"@join:value 中 value 值 (.+?) 不合法！必须为 &/Table0,</Table1/key1,@/Table1:alias2/key2,\\.\\.\\. 或 (.+?), '</Table1/key1':(.+?),\\.\\.\\. \\} 这种形式！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractParser.java","lineNumber":1616,"sourceCode":"\t\t\tLog.e(TAG, \"onJoinParse  set == null || set.isEmpty() >> return null;\");\n\t\t\treturn null;\n\t\t}\n\n\t\tList<Join<T, M, L>> joinList = new ArrayList<>();\n\n\t\tfor (Entry<String, Object> e : set) {  // { &/User:{}, </Moment/id@\":{}, @/Comment/toId@:{} }\n\t\t\t// 分割 /Table/key\n\t\t\tString path = e == null ? null : e.getKey();\n\t\t\tObject outer = path == null ? null : e.getValue();\n\n\t\t\tif (outer instanceof Map<?, ?> == false) {\n\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":value 中value不合法！\"\n\t\t\t\t\t\t+ \"必须为 &/Table0/key0,</Table1/key1,... 或 { '&/Table0/key0':{}, '</Table1/key1':{},... } 这种形式！\");\n\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);","sourceCodeStart":1598,"sourceCodeEnd":1634,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1598-L1634","documentation":"Each @join entry path must contain '/' separating join type from Table(/key): '&/User/id'. If indexOf(\"/\") < 0 the path has no slash and IllegalArgumentException is thrown listing the required forms. This fires after the value-shape check, when the key itself is malformed.","triggerScenarios":"\"@join\":\"&User\" or map key \"&User\" (missing slash), or an empty-string join type without slash like \"User\". Also a lone '/' edge or leading whitespace corrupting indexOf.","commonSituations":"Omitting the join-type sigil; using '@' APP JOIN as '@User' without slash; hand-built strings; trimming or encoding steps that strip the slash.","solutions":["Always include the slash: '&/User', '@/Comment/toId' etc.","Pick a valid join type prefix (& | ! < > ( ) <> () * @) before the slash","Prefer the map form to make each path visually explicit and testable","Add a client-side regex check like ^[&|!<>()*@]?/\\w+(/\\w+)?$ per entry"],"exampleFix":"// before\n{\"@join\":\"&User\"}\n// after\n{\"@join\":\"&/User/id\"}","handlingStrategy":"validation","validationCode":"for (String p : joinPaths) {\n  if (!p.matches(\"^[&|!<>()*@]{0,2}/\\\\w+(:\\\\w+)?(/\\\\w+@?)?$\")) throw new IllegalArgumentException(\"bad join path: \" + p);\n}","typeGuard":"boolean joinPathHasSlash(String path) { return path != null && path.indexOf('/') > 0; }","tryCatchPattern":null,"preventionTips":["Generate join paths from (type, table, key) triples so a slash is always inserted","Regex-validate every path client-side","Prefer map form to make malformed keys visible in review"],"tags":["apijson","join","path-validation","request-format"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}