JSON scalar is not converted to Map
I'm currently facing an issue when JSON scalar is not converted into a Map correctly.
To my understanding, the issue is with DataBinder that tries to convert the value. In case when the target type is a Map interface, it will fail on a line spring-graphql-1.0.1-sources.jar!/org/springframework/graphql/data/GraphQlArgumentBinder.java:248 with error saying that it cannot find constructor. And when switching to a concrete implementation like HashMap, an empty map will be a result of conversion because of BeanPropertyBindingResult.
I'm not sure whether this is an expected behaviour to be honest, but I assume that this is a bug as the conversion works in case when target type is List<Map<.., ..>>
I have faced this same issue.
I couldn't really find a solution other than implementing my own custom coercing approach (JsonNodeCoearcing) - instead of using Map I have used Jackson's JsonNode as target conversion type.
Check this question I posted on StackOverflow, I have also added the custom coercing solution details there: https://stackoverflow.com/questions/72915766/spring-graphql-how-to-map-json-extended-scalar-to-pojo-attribute
I'm not sure if there is an official approach to work around this and not requiring custom coercing impl.
@bduisenov could you share a sample application showing the issue?
@djselzlein I think this might be a different problem. In your example, it looks like you're assuming that Jackson is used to deserialize the JSON input: it's not the case. GraphQL Java is itself deserializing the JSON input as Maps and applying scalars and coercion along the way. After that, Spring GraphQL will try to bind those values to POJOs. As a consequence, the @JsonDeserialize annotation on your POJO will never be used for deserialization. I think scalars and coercion are the expected way to go, although dealing with JsonNode objects as part of the input payload looks a bit strange to me.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.
@bclozel here's a test and sample application showing (what I understand to be) the issue:
https://github.com/jjavery/json-scalar-bug/blob/main/src/test/java/com/example/jsonscalar/controller/ExampleControllerTests.java
In the schema, the CreateExampleInput input contains a JSON scalar named data. This should be converted to Map<String, Object> and assigned to the data property in a CreateExampleInput instance, but an empty HashMap<> is assigned instead.
The JSON scalar is from com.graphql-java:graphql-java-extended-scalars:18.1
Thanks for the sample.
The underlying problem seems to be that our @Argument support doesn't support binding to Map. If the the Object has getters/setters for which we use a DataBinder, we use a regular nested path myMap.myKey instead of an indexed one myMap[myKey]. For an Object with constructor arg, we try to create and initialize as a regular Object, which doesn't work for a Map.
It would be relatively straight forward to address this for the constructor case because we know that the target type is Map so I'll schedule that for 1.0.3. It would be more difficult to address for the getter/setter case, because when we build the property paths for the data binding without awareness of the target field type. It's something that can be done but requires more work.
For 1.0.x, this has been fixed for the constructor based initialization. That means, in the example from @jjavery, the CreateExampleInput type would change constructor injection:
@Data
@AllArgsConstructor
public class CreateExampleInput {
private String name;
private Map<String, Object> data;
}
For 1.1.x, we'll address this also for bean property initialization, in #516.