I am implementing GraphQL API using Spring for GraphQL project and the GraphQL Java Extended Scalars project for handling JSON attributes since the data attribute I have is dynamic and its structure is not known by the server.
This JSON attribute is part of the payload in a mutation input, which POJO looks like this:
@Accessors(fluent = true)
@Builder
@Data
@JsonDeserialize(builder = MutationInputModel.MutationInputModelBuilder.class)
public class MutationInputModel {
private JsonNode data;
...
}
As exemplified above, I am using Lombok annotations for deserialization and accessors generation. The data
attribute is Jackson's JsonNode
type.
However, when calling this mutation I'm getting the following exception:
org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.fasterxml.jackson.databind.JsonNode]: Is it an abstract class?; nested exception is java.lang.InstantiationException
On a previous GraphQL API implementation using this library this same model would work just fine.
My question is how to properly setup JSON scalar type on the POJOs? I have tried Jackson's ObjectNode
and Map<String, Object>
with no luck neither.
I ended up implementing my own custom coercing approach so I'll share here for further reference:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collections;
import java.util.Map;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import static graphql.scalars.ExtendedScalars.Object;
/**
* Custom Coercing implementation in order to parse GQL Objects,
* which get mapped as LinkedHashMap instances by {@link graphql.scalars.object.ObjectScalar}
* into Jackson's JsonNode objects.
*/
public class JsonNodeCoercing implements Coercing<JsonNode, Object> {
private static final Coercing<?, ?> COERCING = Object.getCoercing();
private final ObjectMapper objectMapper;
public JsonNodeCoercing(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public Object serialize(Object input) throws CoercingSerializeException {
return input;
}
@Override
public JsonNode parseValue(Object input) throws CoercingParseValueException {
return objectMapper.valueToTree(input);
}
@Override
public JsonNode parseLiteral(Object input) throws CoercingParseLiteralException {
return parseLiteral(input, Collections.emptyMap());
}
@Override
public JsonNode parseLiteral(Object input, Map<String, Object> variables) throws CoercingParseLiteralException {
return objectMapper.valueToTree(COERCING.parseLiteral(input, variables));
}
}
And then this is how the scalar bean config looks like:
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(ObjectMapper objectMapper) {
GraphQLScalarType jsonScalarType = GraphQLScalarType.newScalar()
.name("JSON")
.description("A JSON scalar")
.coercing(new JsonNodeCoercing(objectMapper))
.build();
return wiringBuilder -> wiringBuilder
.scalar(jsonScalarType);
}
If anyone has alternate/simpler approach please share.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.