简体   繁体   中英

Spring GraphQL - how to map JSON extended scalar to POJO attribute?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM