{"record":{"id":"90fe8e876b5dba55","repo":"Tencent/APIJSON","slug":"table-k-join-table-join-table-k","errorCode":null,"errorMessage":"${table}/${k} 不合法！join 关联的 Table 中，join: ?/Table/key 时只能有 1 个 key@:value；join: ?/Table 时所有 key@:value 要么是符合 join 格式，要么能直接解析成具体值！","messagePattern":"(.+?)/(.+?) 不合法！join 关联的 Table 中，join: \\?/Table/key 时只能有 1 个 key@:value；join: \\?/Table 时所有 key@:value 要么是符合 join 格式，要么能直接解析成具体值！","errorType":"validation","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractParser.java","lineNumber":1756,"sourceCode":"\n\t\t\t\t\t\t\t\tif (StringUtil.isName(k.substring(0, k.length() - 1)) == false) {\n\t\t\t\t\t\t\t\t\tthrow new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + \":'\" + e.getKey() + \"' 中 \" + k + \" 不合法 ！\" +\n\t\t\t\t\t\t\t\t\t\t\t\"@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接！\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\trefObj.put(k, v);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tObject rv = getValueByPath(sv);\n\t\t\t\t\tif (rv != null && rv.equals(sv) == false) {\n\t\t\t\t\t\trequestObj.put(k.substring(0, k.length() - 1), rv);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow new UnsupportedOperationException(table + \"/\" + k + \" 不合法！\" + apijson.JSONRequest.KEY_JOIN + \" 关联的 Table 中，\"\n\t\t\t\t\t\t\t+ \"join: ?/Table/key 时只能有 1 个 key@:value；join: ?/Table 时所有 key@:value 要么是符合 join 格式，要么能直接解析成具体值！\");  // TODO 支持 join on\n\t\t\t\t}\n\n\t\t\t\tif (k.startsWith(\"@\")) {\n\t\t\t\t\tif (JOIN_COPY_KEY_LIST.contains(k)) {\n\t\t\t\t\t\trequestObj.put(k, v); // 保留\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (k.endsWith(\"@\")) {\n\t\t\t\t\t\tthrow new UnsupportedOperationException(table + \"/\" + k + \" 不合法！\" + apijson.JSONRequest.KEY_JOIN + \" 关联的 Table 中，\"\n\t\t\t\t\t\t\t\t+ \"join: ?/Table/key 时只能有 1 个 key@:value；join: ?/Table 时所有 key@:value 要么是符合 join 格式，要么能直接解析成具体值！\");  // TODO 支持 join on\n\t\t\t\t\t}\n\n\t\t\t\t\tif (k.contains(\"()\") == false) { // 不需要远程函数\n\t\t\t\t\t\trequestObj.put(k, v); // 保留\n\t\t\t\t\t}\n\t\t\t\t}","sourceCodeStart":1738,"sourceCodeEnd":1774,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L1738-L1774","documentation":"A key@ entry in the joined table object had a path value that the parser could not resolve: getValueByPath(sv) returned null or echoed the input string back, meaning '/Table/key' does not point to an existing value in the current request. Since join: ?/Table/key permits exactly one key@ and join: ?/Table requires every key@ to be resolvable, an UnsupportedOperationException is raised (a TODO marks future JOIN ON support).","triggerScenarios":"{ 'join': '</Moment', 'Moment': { 'userId@': '/User/id' } } sent without a 'User' object in the same request; or '/User/name' where 'name' is not a key of User; or a path with a typo '/Userr/id'.","commonSituations":"Forgetting to include the referenced (main) table object in the same request; referencing a key the main table object does not declare; renaming tables after the join request was written.","solutions":["Include the referenced table object with the referenced key in the same request: add 'User': { 'id': ... }","Fix typos in the reference path so '/targetTable/targetKey' matches real keys already present","Verify the target key exists in the referenced object (it must be literal-valued, not another unresolved reference)"],"exampleFix":"// before\n{ 'join': '</Moment', 'Moment': { 'userId@': '/User/id' } }\n// after\n{ 'join': '</Moment', 'Moment': { 'userId@': '/User/id' }, 'User': { 'id': 38710 } }","handlingStrategy":"validation","validationCode":"function resolveRefPath(req, path) {\n  if (typeof path !== 'string' || !path.startsWith('/')) return null;\n  let cur = req;\n  for (const seg of path.split('/').filter(Boolean)) {\n    if (cur == null || typeof cur !== 'object' || !(seg in cur)) return null;\n    cur = cur[seg];\n  }\n  return cur;\n}\nfunction checkJoinRefs(req) {\n  for (const [tblKey, tbl] of Object.entries(req)) {\n    if (tbl && typeof tbl === 'object') {\n      for (const [k, v] of Object.entries(tbl)) {\n        if (k.endsWith('@') && typeof v === 'string' && v.startsWith('/') && resolveRefPath(req, v) == null) {\n          return `${tblKey}/${k}: path ${v} does not resolve in this request`;\n        }\n      }\n    }\n  }\n  return null;\n}","typeGuard":null,"tryCatchPattern":"Catch UnsupportedOperationException containing '只能有 1 个 key@:value'; re-check that every /Table/key path exists in the request payload and report the unresolved one.","preventionTips":["Every key@ path must point to an existing literal value inside the same request","Automated test: walk all key@ values and assert they resolve before shipping the request builder"],"tags":["apijson","join","reference-assignment","path-resolution"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}