{"record":{"id":"ff29bd291436a367","repo":"Tencent/APIJSON","slug":"join-e-getkey-tablekey-key-valu","errorCode":null,"errorMessage":"join:${e.getKey()}' 对应的 ${tableKey}:{ ${key}: value } 中 value 类型不合法！必须为同层级引用赋值路径 String！","messagePattern":"join:(.+?)' 对应的 (.+?):(.+?) 中 value 类型不合法！必须为同层级引用赋值路径 String！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractParser.java","lineNumber":1692,"sourceCode":"\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\n\t\t\tSet<Entry<String, Object>> tableSet = tableObj.entrySet();\n\t\t\t// 取出所有 join 条件\n\t\t\tM requestObj = JSON.createJSONObject(); // (Map<String, Object>) obj.clone();\n\n\t\t\tboolean matchSingle = false;\n\t\t\tfor (Entry<String, Object> tableEntry : tableSet) {","sourceCodeStart":1674,"sourceCodeEnd":1710,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1674-L1710","documentation":"When the join path names an explicit ON key (e.g. '@/Comment/toId@'), the joined table object must contain that key with a String value holding the same-layer reference-assignment path. This IllegalArgumentException is thrown when tableObj.get(key) is not a String (number, object, array, boolean, or null).","triggerScenarios":"{ 'join': '@/Comment/toId@', 'Comment': { 'toId@': 38710 } } — the ON key exists but its value is a literal id instead of a path String like '/User/id'; also 'toId@': { '/User/id': {} } or a missing/null value.","commonSituations":"Writing the actual column value where the reference path is expected; mixing up which side of the join holds the path; converting an existing WHERE condition into a join without changing the value format.","solutions":["Change the value of the key@ entry inside the joined table object to a reference path String: 'toId@': '/User/id'","If you meant a literal value condition, keep it as a normal key ('toId': 38710) and do not name it in the join path"],"exampleFix":"// before\n{ 'join': '@/Comment/toId@', 'Comment': { 'toId@': 38710 } }\n// after\n{ 'join': '@/Comment/toId@', 'Comment': { 'toId@': '/User/id' } }","handlingStrategy":"type-guard","validationCode":"function checkOnValueTypes(req, joinValue) {\n  const paths = typeof joinValue === 'string' ? [joinValue] : Object.keys(joinValue);\n  for (const p of paths) {\n    const rest = p.substring(p.indexOf('/') + 1);\n    const parts = rest.split('/');\n    const tableKey = parts[parts.length - 2], key = parts[parts.length - 1];\n    const tbl = req[tableKey.split(':')[0]];\n    if (key.endsWith('@') && tbl != null && typeof tbl[key] !== 'string') {\n      return `${tableKey}.${key} must be a String reference path, got ${typeof tbl[key]}`;\n    }\n  }\n  return null;\n}","typeGuard":"const isRefPath = (v) => typeof v === 'string' && v.startsWith('/');","tryCatchPattern":"Catch IllegalArgumentException with '必须为同层级引用赋值路径 String'; point the user at the named key@ entry and coerce it to '/Table/key' form.","preventionTips":["key@ values are always Strings starting with '/', never literal ids","Keep literals under plain keys and reference paths under key@ keys"],"tags":["apijson","join","type-error","reference-assignment"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}