Context
Upstream issue kobylynskyi#559
asked for request parameters to be sent in the standard "variables" JSON section
({"query": "query($name:String){...}", "variables": {"name":"x"}}) instead of being
inlined into the query string. It was closed as a question with the reasoning that
servers accept inlined literals fine.
That reasoning misses real cases:
- Query complexity / depth limits: many servers (e.g. graphql-java's
MaxQueryComplexityInstrumentation, Apollo Gateway, Hasura) compute complexity over
the parsed document; inlined literals inflate the query text and defeat
normalized-query caching / persisted queries, since every distinct value
produces a different query document.
- Strict endpoints (the original reporter's ArangoDB Foxx case) that require proper
variables separation.
Verification (this fork, current code)
Behavior is exactly as described in kobylynskyi#559 — variables are not supported:
GraphQLRequestSerializer.buildQuery() inlines every input map entry via getEntry()
(src/main/java/io/github/besi97/graphql/codegen/model/graphql/GraphQLRequestSerializer.java:100).
jsonQuery() emits only {"query":"..."}; no variables key is ever produced (:139).
"variables" appears nowhere in src/main or src/test.
- Generated request classes only carry
Map<String, Object> input — no GraphQL type info
is available at runtime, so variable definitions ($name: String!) cannot even be built.
Proposed design (full scope)
Variables become the default for freshly generated code; a codegen option
(serializeRequestInputAsVariables, default true) reverts to inlined literals.
Top-level operation arguments
GraphQLOperationRequest: add default Map<String, String> getParamToGraphQLTypeMap()
(arg name → GraphQL type, e.g. "id" -> "ID!"). Type map present ⇒ variables mode;
absent ⇒ inline (backward compatible for hand-written requests).
ParameterDefinition: add graphQLType; populate in InputValueDefinitionToParameterMapper
via a new recursive AST→string helper in GraphQLTypeMapper ([ID!]!, etc.).
- Emit the map in
java-lang/request.ftl + kotlin-lang/request.ftl.
GraphQLParametrizedInput + both parametrized_input.ftls: add getInput() returning the
raw field values (fields are private, no getters today) so nested input objects can be
converted to JSON.
- Serializer: args render as
$name; declarations prepended to the operation
(query Op($x: Int!) {...}, also valid anonymously: query($x: Int)); body becomes
{"query":"...","variables":{...}}. Values converted with a Jackson mapper copy using
NON_NULL inclusion (enums → name, Input POJOs → Jackson, useObjectMapperForInputSerialization
fields → JSON string). All-or-nothing fallback to inline if any non-null arg lacks a type.
- Batch (
GraphQLRequests): merge declarations; dedupe identical name+type, rename conflicts
($name__2).
Nested field arguments in response projections
Fields like type Client { data(ID: Int!): Data! } currently inline 5 into
query clients { data(ID: 5) {...} } — same problem, one level down.
GraphQLResponseProjection / GraphQLResponseField carry no arg-type info today;
values only appear via ParametrizedInput.toString() (GraphQL literal syntax).
- Generated projections must expose per-field arg name → GraphQL type.
- Serializer walks the whole projection tree at serialization time, hoists every nested
arg into a uniquely-named variable (same field with different args appears multiple
times), and merges all declarations into the operation.
- This roughly doubles the change surface vs. top-level-only, but doing it halfway would
leave a confusing mode where some args are variables and others are literals.
Tests
GraphQLRequestSerializerTest: variables path, null skipping, enums/lists/nested inputs/
custom scalars, anonymous ops, batch merge + conflict rename, fallback.
GraphQLCodegenRequestTest: type map present by default / absent with option off;
regenerate affected expected-classes/request/* (Java + Kotlin).
Docs
- README codegen-options table row for
serializeRequestInputAsVariables.
Context
Upstream issue kobylynskyi#559
asked for request parameters to be sent in the standard
"variables"JSON section(
{"query": "query($name:String){...}", "variables": {"name":"x"}}) instead of beinginlined into the query string. It was closed as a
questionwith the reasoning thatservers accept inlined literals fine.
That reasoning misses real cases:
MaxQueryComplexityInstrumentation, Apollo Gateway, Hasura) compute complexity overthe parsed document; inlined literals inflate the query text and defeat
normalized-query caching / persisted queries, since every distinct value
produces a different query document.
variables separation.
Verification (this fork, current code)
Behavior is exactly as described in kobylynskyi#559 — variables are not supported:
GraphQLRequestSerializer.buildQuery()inlines everyinputmap entry viagetEntry()(
src/main/java/io/github/besi97/graphql/codegen/model/graphql/GraphQLRequestSerializer.java:100).jsonQuery()emits only{"query":"..."}; novariableskey is ever produced (:139)."variables"appears nowhere insrc/mainorsrc/test.Map<String, Object> input— no GraphQL type infois available at runtime, so variable definitions (
$name: String!) cannot even be built.Proposed design (full scope)
Variables become the default for freshly generated code; a codegen option
(
serializeRequestInputAsVariables, defaulttrue) reverts to inlined literals.Top-level operation arguments
GraphQLOperationRequest: adddefault Map<String, String> getParamToGraphQLTypeMap()(arg name → GraphQL type, e.g.
"id" -> "ID!"). Type map present ⇒ variables mode;absent ⇒ inline (backward compatible for hand-written requests).
ParameterDefinition: addgraphQLType; populate inInputValueDefinitionToParameterMappervia a new recursive AST→string helper in
GraphQLTypeMapper([ID!]!, etc.).java-lang/request.ftl+kotlin-lang/request.ftl.GraphQLParametrizedInput+ bothparametrized_input.ftls: addgetInput()returning theraw field values (fields are private, no getters today) so nested input objects can be
converted to JSON.
$name; declarations prepended to the operation(
query Op($x: Int!) {...}, also valid anonymously:query($x: Int)); body becomes{"query":"...","variables":{...}}. Values converted with a Jackson mapper copy usingNON_NULLinclusion (enums → name, Input POJOs → Jackson,useObjectMapperForInputSerializationfields → JSON string). All-or-nothing fallback to inline if any non-null arg lacks a type.
GraphQLRequests): merge declarations; dedupe identical name+type, rename conflicts(
$name__2).Nested field arguments in response projections
Fields like
type Client { data(ID: Int!): Data! }currently inline5intoquery clients { data(ID: 5) {...} }— same problem, one level down.GraphQLResponseProjection/GraphQLResponseFieldcarry no arg-type info today;values only appear via
ParametrizedInput.toString()(GraphQL literal syntax).arg into a uniquely-named variable (same field with different args appears multiple
times), and merges all declarations into the operation.
leave a confusing mode where some args are variables and others are literals.
Tests
GraphQLRequestSerializerTest: variables path, null skipping, enums/lists/nested inputs/custom scalars, anonymous ops, batch merge + conflict rename, fallback.
GraphQLCodegenRequestTest: type map present by default / absent with option off;regenerate affected
expected-classes/request/*(Java + Kotlin).Docs
serializeRequestInputAsVariables.