{"record":{"id":"77d32863877a6800","repo":"Tencent/APIJSON","slug":"post-key-value-key-1","errorCode":null,"errorMessage":"POST请求: 每一个 key:value 中的key都必须是1个单词！","messagePattern":"POST请求: 每一个 key:value 中的key都必须是1个单词！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java","lineNumber":2388,"sourceCode":"\t\t\t\t\t}\n\n\t\t\t\t\treturn \"count(\" + c0 + \")\" + as + q + JSONResponse.KEY_COUNT + q;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn \"count(\" + (onlyOne ? gainKey(c0) : \"*\") + \")\" + as + q + JSONResponse.KEY_COUNT + q;\n\t\t\t//\t\t\treturn SQL.count(onlyOne && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : \"*\");\n\t\tcase POST:\n\t\t\tif (column == null || column.isEmpty()) {\n\t\t\t\tthrow new IllegalArgumentException(\"POST 请求必须在Table内设置要保存的 key:value ！\");\n\t\t\t}\n\n\t\t\tString s = \"\";\n\t\t\tboolean pfirst = true;\n\t\t\tfor (String c : column) {\n\t\t\t\tif (isPrepared() && StringUtil.isName(c) == false) {\n\t\t\t\t\t// 不能通过 ? 来代替，SELECT 'id','name' 返回的就是 id:\"id\", name:\"name\"，而不是数据库里的值！\n\t\t\t\t\tthrow new IllegalArgumentException(\"POST请求: 每一个 key:value 中的key都必须是1个单词！\");\n\t\t\t\t}\n\t\t\t\ts += ((pfirst ? \"\" : \",\") + gainKey(c));\n\n\t\t\t\tpfirst = false;\n\t\t\t}\n\n\t\t\treturn \"(\" + s + \")\";\n\t\tcase GET:\n\t\tcase GETS:\n\t\t\tString joinColumn = \"\";\n\t\t\tif (joinList != null) {\n\t\t\t\tboolean first = true;\n\t\t\t\tfor (Join<T, M, L> join : joinList) {\n\t\t\t\t\tif (join.isAppJoin()) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tSQLConfig<T, M, L> ocfg = join.getOnConfig();","sourceCodeStart":2370,"sourceCodeEnd":2406,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java#L2370-L2406","documentation":"Thrown on POST in prepared mode when one of the keys to insert fails StringUtil.isName — every column key in the table object must be a single identifier word. Because prepared statements cannot parameterize column names, a malformed key cannot be safely quoted and is rejected outright.","triggerScenarios":"POST body {\"User\":{\"user name\":\"x\"}} (space in key), {\"User\":{\"user.id\":1}} (dot), {\"User\":{\"123abc\":1}} (leading digit), or keys containing operators/quotes injected via user input mapped directly to JSON keys.","commonSituations":"Dynamically building the POST body from a map whose keys come from user input or a spreadsheet header; key/value confusion where a value ends up as a key; Unicode or invisible whitespace in keys copied from documents.","solutions":["Sanitize keys client-side: match /^[A-Za-z][A-Za-z0-9_]*$/ before adding them to the table object.","If a column name legitimately contains special characters, wrap it in backticks in the key: \"`my-col`\" is handled elsewhere, but plain keys must be single words.","Whitelist allowed column names per table on the server instead of accepting arbitrary keys.","Log the offending key from the exception message to locate the producer."],"exampleFix":"// before\n{\"User\":{\"first name\":\"Tom\"}}\n// after\n{\"User\":{\"first_name\":\"Tom\"}}","handlingStrategy":"validation","validationCode":"const NAME = /^[A-Za-z][A-Za-z0-9_]*$/;\nfor (const k of Object.keys(table)) { if (!k.startsWith('@') && !NAME.test(k)) throw new Error('key not a word: ' + k); }","typeGuard":"function isInsertKey(k) { return /^[@`]/.test(k) || /^[A-Za-z][A-Za-z0-9_]*$/.test(k); }","tryCatchPattern":"catch IllegalArgumentException; reject request client-side once keys are validated","preventionTips":["Whitelist insertable columns per table","Never map raw user text to JSON keys","Use snake_case single-word column names in your schema"],"tags":["apijson","sql","input-validation","post-request","column-name"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}