{"record":{"id":"8d2fa70cb7e85b31","repo":"apache/druid","slug":"must-have-an-even-number-of-arguments","errorCode":null,"errorMessage":"must have an even number of arguments","messagePattern":"must have an even number of arguments","errorType":"validation","errorClass":"DruidException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java","lineNumber":67,"sourceCode":"public class NestedDataExpressions\n{\n  private static ExpressionType JSON_ARRAY = ExpressionTypeFactory.getInstance().ofArray(ExpressionType.NESTED_DATA);\n\n  public static class JsonObjectExprMacro implements ExprMacroTable.ExprMacro\n  {\n    public static final String NAME = \"json_object\";\n\n    @Override\n    public String name()\n    {\n      return NAME;\n    }\n\n    @Override\n    public Expr apply(List<Expr> args)\n    {\n      if (args.size() % 2 != 0) {\n        throw validationFailed(\"must have an even number of arguments\");\n      }\n\n      class StructExpr extends ExprMacroTable.BaseScalarMacroFunctionExpr\n      {\n        public StructExpr(List<Expr> args)\n        {\n          super(JsonObjectExprMacro.this, args);\n        }\n\n        @Override\n        public ExprEval eval(ObjectBinding bindings)\n        {\n          HashMap<String, Object> theMap = new HashMap<>();\n          for (int i = 0; i < args.size(); i += 2) {\n            ExprEval field = args.get(i).eval(bindings);\n            ExprEval value = args.get(i + 1).eval(bindings);\n\n            if (!field.type().is(ExprType.STRING)) {","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java#L49-L85","documentation":"Druid's JSON_OBJECT nested-data macro builds an object from alternating key/value arguments, so it requires an even argument count. Druid throws a validation failure at expression compilation when an odd number of arguments is passed, since at least one key would lack a value.","triggerScenarios":"Calling JSON_OBJECT(k1, v1, k2) — any odd-sized argument list to the JSON_OBJECT expression macro in a Druid SQL query or native expression selector.","commonSituations":"Programmatic query builders that append a value but skip its key (or vice versa); hand-written SQL where a key/value pair was deleted or duplicated incompletely; refactoring that dropped a trailing argument.","solutions":["Count the arguments and add the missing key or value so pairs are complete: JSON_OBJECT('a', 1, 'b', 2).","If a pair is not needed, remove both its key and value arguments.","In generated SQL, assert args.size() % 2 == 0 before emitting the JSON_OBJECT call to fail early in your own code."],"exampleFix":"-- before\nSELECT JSON_OBJECT('a', x, 'b') FROM t\n-- after\nSELECT JSON_OBJECT('a', x, 'b', y) FROM t","handlingStrategy":"validation","validationCode":"// JS/TS before emitting SQL\nif (pairs.length % 2 !== 0) throw new Error('JSON_OBJECT requires an even number of key/value arguments');","typeGuard":"function hasEvenArgs(args) { return Array.isArray(args) && args.length % 2 === 0; }","tryCatchPattern":"try {\n  return runDruidQuery(query);\n} catch (ExpressionValidationException e) {\n  if (e.getMessage().contains(\"even number of arguments\")) {\n    throw new UserInputException(\"JSON_OBJECT arguments must come in key/value pairs\");\n  }\n  throw e;\n}","preventionTips":["Build JSON_OBJECT arguments from a Map/pairs array, joining keys and values, so parity is structural.","Add an assertion (args.size() % 2 == 0) in query-generating code.","Review SQL by hand after removing any key/value pair from JSON_OBJECT calls."],"tags":["druid","expression","json","validation","argument-count"],"backgroundTag":"missing-required-argument","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}