{"record":{"id":"c68b2f38d9e31784","repo":"Tencent/APIJSON","slug":"join-value-value-table0-key0-table1-k","errorCode":null,"errorMessage":"@join:value 中value不合法！必须为 &/Table0/key0,</Table1/key1,... 或 { '&/Table0/key0':{}, '</Table1/key1':{},... } 这种形式！","messagePattern":"@join:value 中value不合法！必须为 &/Table0/key0,</Table1/key1,\\.\\.\\. 或 (.+?), '</Table1/key1':(.+?),\\.\\.\\. \\} 这种形式！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractParser.java","lineNumber":1610,"sourceCode":"\t\t}\n\n\n\t\tSet<Entry<String, Object>> set = joinMap == null ? null : new LinkedHashSet<>(slashKeys);\n\n\t\tif (set == null || set.isEmpty()) {\n\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);","sourceCodeStart":1592,"sourceCodeEnd":1628,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1592-L1628","documentation":"When @join is the map form, each entry's VALUE must itself be a Map (e.g. {\"&/User/id\":{}}). If any value is a string/number/array/etc., IllegalArgumentException is thrown describing both accepted notations. This fires before any path parsing, i.e. it is purely a shape check on the join map.","triggerScenarios":"{\"@join\":{\"&/User/id\":\"userId\"}} — attaching a value to a join entry; or {\"@join\":{\"&/User/id\":[]}}. Users try to supply join conditions as scalar values on the entry.","commonSituations":"Assuming the map form carries ON-conditions as values (conditions belong inside the table object instead); converting string form to map form while keeping string values; misreading docs examples with placeholders.","solutions":["Use the plain string form if you need no per-entry options: \"@join\":\"&/User/id\"","Keep map values as empty objects {}; put conditions in the joined table's object in the request","Remove any scalar/array values attached to join entries","Re-check the exact two accepted shapes in the error message"],"exampleFix":"// before\n{\"@join\":{\"&/User/id\":\"contactId\"}}\n// after\n{\"@join\":\"&/User/id\",\"User\":{\"contactId@\":\"/User/id\"}}","handlingStrategy":"validation","validationCode":"if (join instanceof Map) {\n  for (Object v : ((Map<?,?>) join).values()) {\n    if (!(v instanceof Map)) throw new IllegalArgumentException(\"join entry value must be {}\");\n  }\n}","typeGuard":"boolean joinMapValuesAreObjects(Map<?,?> joinMap) {\n  return joinMap.values().stream().allMatch(v -> v instanceof Map);\n}","tryCatchPattern":null,"preventionTips":["Use the string form unless per-entry options are truly needed","Conditions go in the table object, never as join-entry values","Validate the assembled join structure before send"],"tags":["apijson","join","type-validation","request-format"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}