Tencent/APIJSON · error · IllegalArgumentException

join:${e.getKey()} 中 ${k} 不合法!@ APP JOIN 必须有且只有一个引用赋值键值对!

Error message

join:${e.getKey()} 中 ${k} 不合法!@ APP JOIN 必须有且只有一个引用赋值键值对!

What it means

APP JOIN requires exactly one reference-assignment key-value pair ('key@': '/Table/refKey') as its ON condition. While collecting ON candidates from the joined table object, if refObj already holds one entry and another candidate 'k' appears, this IllegalArgumentException is thrown — a second ON condition is not allowed for '@' joins.

Source

Thrown at APIJSONORM/src/main/java/apijson/orm/AbstractParser.java:1735

				matchSingle = matchSingle == false && k.equals(key);
				if (matchSingle) {
					continue;
				}

				if (k.length() > 1 && k.indexOf("@") == k.length() - 1 && v instanceof String) {
					String sv = (String) v;
					int ind = sv.endsWith("@") ? -1 : sv.indexOf("/");
					if (ind == 0 && key == null) {  // 指定了某个就只允许一个 ON 条件
						String p = sv.substring(1);
						int ind2 = p.indexOf("/");
						String tk = ind2 < 0 ? null : p.substring(0, ind2);

						apijson.orm.Entry<String, String> te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true);

						if (te != null && isTableKey(te.getKey()) && request.get(tk) instanceof Map<?, ?>) {
							if (isAppJoin) {
								if (refObj.size() >= 1) {
									throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!"
											+ "@ APP JOIN 必须有且只有一个引用赋值键值对!");
								}

								if (StringUtil.isName(k.substring(0, k.length() - 1)) == false) {
									throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" +
											"@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接!");
								}
							}

							refObj.put(k, v);
							continue;
						}
					}

					Object rv = getValueByPath(sv);
					if (rv != null && rv.equals(sv) == false) {
						requestObj.put(k.substring(0, k.length() - 1), rv);
						continue;

View on GitHub (pinned to 5284052872)

Solutions

  1. Keep only one key@ entry in the joined table object for '@' joins and delete the rest
  2. Switch to a SQL JOIN type ('</Table', '&/Table', '|/Table') which supports multiple ON conditions via key@ pairs
  3. Express the second condition as a normal WHERE-style key on the joined table instead of a reference assignment

Example fix

// before
{ 'join': '@/Comment', 'Comment': { 'toId@': '/User/id', 'momentId@': '/Moment/id' } }
// after
{ 'join': '</Comment/momentId@', 'Comment': { 'toId@': '/User/id', 'momentId@': '/Moment/id' } }
Defensive patterns

Strategy: validation

Validate before calling

function checkAppJoinSingleOn(req, joinValue) {
  const paths = typeof joinValue === 'string' ? [joinValue] : Object.keys(joinValue).filter(p => p.startsWith('@/'));
  for (const p of paths) {
    const rest = p.substring(p.indexOf('/') + 1);
    const parts = rest.split('/');
    const tbl = req[parts[0].split(':')[0]];
    if (tbl) {
      const refs = Object.keys(tbl).filter(k => k.endsWith('@'));
      if (refs.length > 1) return `APP JOIN ${p}: found ${refs.length} key@ pairs (${refs.join(', ')}), max 1`;
    }
  }
  return null;
}

Try / catch

Catch IllegalArgumentException with '必须有且只有一个引用赋值键值对'; downgrade to SQL join type or drop extra key@ pairs.

Prevention

When it happens

Trigger: { 'join': '@/Comment', 'Comment': { 'toId@': '/User/id', 'momentId@': '/Moment/id' } } — two key@ entries both pointing at same-layer tables; APP JOIN executes in memory and cannot combine two ON conditions.

Common situations: Porting a multi-condition SQL JOIN ON (a.id = b.x AND a.oid = b.y) directly to APP JOIN; adding an extra reference-assignment key later without noticing the join is '@' type.

Related errors


AI-assisted analysis of Tencent/APIJSON@5284052872 (2026-08-14). Data as JSON: /api/errors/c0b42300a5e390f7. Report an issue: GitHub.