graphql-spqr: @GraphQLNonNull ignored on Input objects when @JsonProperty is used
Hello,
recently the frontend team informed me that on some of our inputs there are no required fields. I’ve traced down the bug to Jackson’s @JsonProperty
which was added some time ago on constructor parameters (constructor itself was annotated with @JsonCreator
).
Here is the MVCE that produces no !
in generated GQL schema (i.e. every field is nullable):
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import graphql.schema.idl.SchemaPrinter;
import io.leangen.graphql.GraphQLSchemaGenerator;
import io.leangen.graphql.annotations.GraphQLArgument;
import io.leangen.graphql.annotations.GraphQLNonNull;
import io.leangen.graphql.annotations.GraphQLQuery;
class Test {
public static void main(String[] args) {
GraphQLSchemaGenerator schemaGenerator = new GraphQLSchemaGenerator()
.withOperationsFromSingleton(new Test());
System.out.println(new SchemaPrinter().print(schemaGenerator.generate()));
}
@GraphQLQuery(name = "RegisterUser")
public String registerUser(@GraphQLArgument(name = "input") @GraphQLNonNull UserInput input) {
return input.getFirstName();
}
}
class UserInput {
private String firstName;
private String lastName;
@JsonCreator
public UserInput(@JsonProperty("firstName") String firstName,
@JsonProperty("lastName") String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@GraphQLNonNull
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Schema produced:
#Query root
type Query {
RegisterUser(input: UserInputInput!): String @_mappedOperation(operation : "__internal__")
}
input UserInputInput @_mappedType(type : "__internal__") {
firstName: String
lastName: String
}
Parameter input
is okay, but firstName
in UserInputInput
is nullable (not required), and there is @GraphQLNonNull
annotation on getter getFirstName
.
I’ve tried following without success:
- adding setters, and annotation it
@GraphQLNonNull UserInput setFirstName (String name)
- adding setter, but annotating parameter
UserInput setFirstName (@GraphQLNonNull String name)
Correct schema is produced when @JsonProperty
is removed, e.g. with this constructor:
public UserInput(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
(It seems that @JsonCreator
annotation does not matter)
input UserInputInput @_mappedType(type : "__internal__") {
firstName: String!
lastName: String
}
So, I thought that @JsonProperty
is considered as “truth source” because it has JsonProperty.required
field which is by default false
. But, adding @JsonProperty(value = "firstName", required = true)
on constructor parameter (or on private field itself, or both) does not change anything.
I’m using graphql-spqr:0.9.9
and graphql-java:11.0
.
Is this a bug or is there any workaround for this? Maybe using @GraphQLInputField
? (I haven’t used that before).
P.S. Another question, I saw on gitter that someone asked how to get rid of @_mappedOperation
and @_mappedType
, but answer was vague. Is it possible now, did you find out a way?
Thank you in advance for answer and for this very helpful library!
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 15 (5 by maintainers)
@EnilPajic I’ve opened a new issue for
JsonProperty#required
: https://github.com/leangen/graphql-spqr/issues/287Hey everyone. Sorry for the silence on my part, I’m away on a business trip these days.
As for the reason the annotation is apparently ignored… When inputs are mapped using Jackson, the logic is as follows:
@GraphQLInputField
annotated elementSo for the case such as:
There’s no explicitly annotated element, and the constructor is the primary mutator (that’s what will be used to deserialize), so only the field type and the ctor parameter type get merged (getter is not involved), thus the annotation is ignored. (So @Kirintale pretty much nailed it)
To force the logic to take the getter into account, you can mark it with
@GraphQLInputField
. Or move@GraphQLNonNull
to the ctor param or the field.The reason it works the way it does is to avoid the situation where a bunch of unrelated language elements have to be reasoned about at the same time to know what the mapping will be. The field is always taken into account to allow the usage of things like Lombok. The primary mutator is taken into account because that is what will actually be used by Jackson. The only remaining element is the one the user explicitly decides to involve.
SPQR uses those to provide some its functionality, so you can’t get rid of them completely without tricky repercussions… If you meant how to avoid printing them, there were changes in the
SchemaPrinter
that lets you skip directives (includeDirectives
), but that’s coming from graphql-java so I have no control over it. If you need a more granular choice if what to print, I’d suggest making a PR on graphql-java.I’ll read thought the rest of the thread as soon as I get a chance and respond to whatever else there is…
Hi!
Thank you for testing this out. Your understanding of
@JsonCreator
is right, if present, setters won’t be called.But I’ve noticed something: if
@JsonCreator
is removed, the incorrect schema is produced again, e.g. with this code:So, in this case, it seems that
@JsonCreator
does not matter (as stated in my original post). But what matters is the@JsonProperty
declaration on constructor parameters. If I remove them, everything works fine (with@GraphQLNonNull
on getter):The above code indeed produces correct schema, so I assume that
@JsonProperty
is the reason? And if@GraphQLNonNull
is working without@JsonProperty
but not with it, this may be a bug?