Skip to content

Commit 5244588

Browse files
committed
Full support for Jackson/Gson types. Simpler configuration.
- All Jackson & Gson types now work out-of-the-box - withTypeMappers and similar methods are greatly simplified - Added withTypeAliasGroup to mark name collisions as expected Closes #122 Closes #123 Closes #124
1 parent 008df4e commit 5244588

27 files changed

+1460
-342
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.leangen.graphql;
2+
3+
public class ConfigurationException extends IllegalStateException {
4+
5+
ConfigurationException(String s) {
6+
super(s);
7+
}
8+
}

src/main/java/io/leangen/graphql/GraphQLSchemaGenerator.java

Lines changed: 148 additions & 188 deletions
Large diffs are not rendered by default.

src/main/java/io/leangen/graphql/extension/SimpleModule.java

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import io.leangen.graphql.generator.mapping.InputConverter;
55
import io.leangen.graphql.generator.mapping.OutputConverter;
66
import io.leangen.graphql.generator.mapping.TypeMapper;
7-
import io.leangen.graphql.generator.mapping.common.IdAdapter;
87
import io.leangen.graphql.metadata.strategy.query.ResolverBuilder;
98
import io.leangen.graphql.metadata.strategy.type.TypeInfoGenerator;
109

@@ -45,28 +44,22 @@ default Optional<TypeInfoGenerator> getTypeInfoGenerator() {
4544
@Override
4645
default void setUp(SetupContext context) {
4746
if (!getResolverBuilders().isEmpty()) {
48-
context.getSchemaGenerator().withResolverBuilders(
49-
(config, defaults) -> defaults.insert(0, getResolverBuilders().toArray(new ResolverBuilder[0])));
47+
context.getSchemaGenerator().withResolverBuilders(getResolverBuilders().toArray(new ResolverBuilder[0]));
5048
}
5149
if (!getNestedResolverBuilders().isEmpty()) {
52-
context.getSchemaGenerator().withNestedResolverBuilders(
53-
(config, defaults) -> defaults.insert(0, getNestedResolverBuilders().toArray(new ResolverBuilder[0])));
50+
context.getSchemaGenerator().withNestedResolverBuilders(getNestedResolverBuilders().toArray(new ResolverBuilder[0]));
5451
}
5552
if (!getTypeMappers().isEmpty()) {
56-
context.getSchemaGenerator().withTypeMappers(
57-
(config, defaults) -> defaults.insertAfter(IdAdapter.class, getTypeMappers().toArray(new TypeMapper[0])));
53+
context.getSchemaGenerator().withTypeMappers(getTypeMappers().toArray(new TypeMapper[0]));
5854
}
5955
if (!getOutputConverters().isEmpty()) {
60-
context.getSchemaGenerator().withOutputConverters(
61-
(config, defaults) -> defaults.insertAfter(IdAdapter.class, getOutputConverters().toArray(new OutputConverter[0])));
56+
context.getSchemaGenerator().withOutputConverters(getOutputConverters().toArray(new OutputConverter[0]));
6257
}
6358
if (!getInputConverters().isEmpty()) {
64-
context.getSchemaGenerator().withInputConverters(
65-
(config, defaults) -> defaults.insert(0, getInputConverters().toArray(new InputConverter[0])));
59+
context.getSchemaGenerator().withInputConverters(getInputConverters().toArray(new InputConverter[0]));
6660
}
6761
if (!getArgumentInjectors().isEmpty()) {
68-
context.getSchemaGenerator().withArgumentInjectors(
69-
(config, defaults) -> defaults.insert(0, getArgumentInjectors().toArray(new ArgumentInjector[0])));
62+
context.getSchemaGenerator().withArgumentInjectors(getArgumentInjectors().toArray(new ArgumentInjector[0]));
7063
}
7164
if (getTypeInfoGenerator().isPresent()) {
7265
context.getSchemaGenerator().withTypeInfoGenerator(getTypeInfoGenerator().get());
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.leangen.graphql.extension.common.gson;
2+
3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonElement;
5+
import io.leangen.geantyref.GenericTypeReflector;
6+
import io.leangen.graphql.execution.ResolutionEnvironment;
7+
import io.leangen.graphql.generator.mapping.OutputConverter;
8+
import io.leangen.graphql.generator.mapping.common.AbstractTypeSubstitutingMapper;
9+
10+
import java.lang.reflect.AnnotatedType;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
public class GsonArrayAdapter extends AbstractTypeSubstitutingMapper<List<JsonElement>> implements OutputConverter<JsonArray, List> {
15+
16+
private static final AnnotatedType JSON = GenericTypeReflector.annotate(JsonElement.class);
17+
18+
@Override
19+
public List convertOutput(JsonArray original, AnnotatedType type, ResolutionEnvironment resolutionEnvironment) {
20+
List<Object> elements = new ArrayList<>(original.size());
21+
original.forEach(element -> elements.add(resolutionEnvironment.convertOutput(element, JSON)));
22+
return elements;
23+
}
24+
25+
@Override
26+
public boolean supports(AnnotatedType type) {
27+
return GenericTypeReflector.isSuperType(JsonArray.class, type.getType());
28+
}
29+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.leangen.graphql.extension.common.gson;
2+
3+
import io.leangen.graphql.extension.SimpleModule;
4+
import io.leangen.graphql.generator.mapping.OutputConverter;
5+
import io.leangen.graphql.generator.mapping.TypeMapper;
6+
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
import java.util.List;
10+
11+
public class GsonModule implements SimpleModule {
12+
13+
private static final GsonArrayAdapter arrayAdapter = new GsonArrayAdapter();
14+
15+
@Override
16+
public List<TypeMapper> getTypeMappers() {
17+
return Arrays.asList(new GsonScalarTypeMapper(), arrayAdapter);
18+
}
19+
20+
@Override
21+
public List<OutputConverter<?, ?>> getOutputConverters() {
22+
return Collections.singletonList(arrayAdapter);
23+
}
24+
25+
@Override
26+
public void setUp(SetupContext context) {
27+
if (!getTypeMappers().isEmpty()) {
28+
context.getSchemaGenerator().withTypeMappersPrepended(getTypeMappers().toArray(new TypeMapper[0]));
29+
}
30+
if (!getOutputConverters().isEmpty()) {
31+
context.getSchemaGenerator().withOutputConvertersPrepended(getOutputConverters().toArray(new OutputConverter[0]));
32+
}
33+
}
34+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.leangen.graphql.extension.common.gson;
2+
3+
import graphql.schema.GraphQLScalarType;
4+
import io.leangen.graphql.generator.BuildContext;
5+
import io.leangen.graphql.generator.OperationMapper;
6+
import io.leangen.graphql.generator.mapping.TypeMapper;
7+
8+
import java.lang.reflect.AnnotatedType;
9+
10+
public class GsonScalarTypeMapper implements TypeMapper {
11+
12+
@Override
13+
public GraphQLScalarType toGraphQLType(AnnotatedType javaType, OperationMapper operationMapper, BuildContext buildContext) {
14+
return GsonScalars.toGraphQLScalarType(javaType.getType());
15+
}
16+
17+
@Override
18+
public GraphQLScalarType toGraphQLInputType(AnnotatedType javaType, OperationMapper operationMapper, BuildContext buildContext) {
19+
return toGraphQLType(javaType, operationMapper, buildContext);
20+
}
21+
22+
@Override
23+
public boolean supports(AnnotatedType type) {
24+
return GsonScalars.isScalar(type.getType());
25+
}
26+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package io.leangen.graphql.extension.common.gson;
2+
3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonElement;
5+
import com.google.gson.JsonNull;
6+
import com.google.gson.JsonObject;
7+
import com.google.gson.JsonPrimitive;
8+
import graphql.language.ArrayValue;
9+
import graphql.language.BooleanValue;
10+
import graphql.language.EnumValue;
11+
import graphql.language.FloatValue;
12+
import graphql.language.IntValue;
13+
import graphql.language.NullValue;
14+
import graphql.language.ObjectValue;
15+
import graphql.language.StringValue;
16+
import graphql.language.Value;
17+
import graphql.schema.Coercing;
18+
import graphql.schema.CoercingParseLiteralException;
19+
import graphql.schema.GraphQLScalarType;
20+
21+
import java.lang.reflect.Type;
22+
import java.util.Collections;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
import static io.leangen.graphql.util.Scalars.literalOrException;
28+
import static io.leangen.graphql.util.Scalars.literalParsingException;
29+
import static io.leangen.graphql.util.Scalars.serializationException;
30+
import static io.leangen.graphql.util.Scalars.valueParsingException;
31+
32+
public class GsonScalars {
33+
34+
public static final GraphQLScalarType JsonAnyNode = new GraphQLScalarType("JSON", "JSON object", new Coercing() {
35+
36+
@Override
37+
public Object serialize(Object dataFetcherResult) {
38+
if (dataFetcherResult instanceof JsonPrimitive) {
39+
return JsonPrimitiveNode.getCoercing().serialize(dataFetcherResult);
40+
}
41+
return dataFetcherResult;
42+
}
43+
44+
@Override
45+
public Object parseValue(Object input) {
46+
return input;
47+
}
48+
49+
@Override
50+
public Object parseLiteral(Object input) {
51+
return parseJsonValue(((Value) input));
52+
}
53+
});
54+
55+
public static final GraphQLScalarType JsonObjectNode = new GraphQLScalarType("JSONObject", "JSON object", new Coercing() {
56+
57+
@Override
58+
public Object serialize(Object dataFetcherResult) {
59+
return dataFetcherResult;
60+
}
61+
62+
@Override
63+
public Object parseValue(Object input) {
64+
return input;
65+
}
66+
67+
@Override
68+
public Object parseLiteral(Object input) {
69+
return parseJsonValue(literalOrException(input, ObjectValue.class));
70+
}
71+
});
72+
73+
public static final GraphQLScalarType JsonPrimitiveNode = new GraphQLScalarType("JSONPrimitive", "A primitive JSON value", new Coercing() {
74+
75+
@Override
76+
public Object serialize(Object dataFetcherResult) {
77+
if (dataFetcherResult instanceof JsonPrimitive) {
78+
JsonPrimitive primitive = (JsonPrimitive) dataFetcherResult;
79+
if (primitive.isString()) {
80+
return primitive.getAsString();
81+
}
82+
if (primitive.isNumber()) {
83+
return primitive.getAsNumber();
84+
}
85+
if (primitive.isBoolean()) {
86+
return primitive.getAsBoolean();
87+
}
88+
if (primitive.isJsonNull()) {
89+
return null;
90+
}
91+
}
92+
throw serializationException(dataFetcherResult, JsonPrimitive.class);
93+
}
94+
95+
@Override
96+
public Object parseValue(Object input) {
97+
if (input instanceof String) {
98+
return new JsonPrimitive((String) input);
99+
}
100+
if (input instanceof Number) {
101+
return new JsonPrimitive((Number) input);
102+
}
103+
if (input instanceof Boolean) {
104+
return new JsonPrimitive((Boolean) input);
105+
}
106+
if (input instanceof Character) {
107+
return new JsonPrimitive((Character) input);
108+
}
109+
throw valueParsingException(input, String.class, Number.class, Boolean.class, Character.class);
110+
}
111+
112+
@Override
113+
public Object parseLiteral(Object input) {
114+
if (input instanceof ObjectValue || input instanceof ArrayValue) {
115+
throw literalParsingException(input, StringValue.class, BooleanValue.class, EnumValue.class,
116+
FloatValue.class, IntValue.class, NullValue.class);
117+
}
118+
return parseJsonValue(((Value) input));
119+
}
120+
});
121+
122+
private static JsonElement parseJsonValue(Value value) {
123+
if (value instanceof BooleanValue) {
124+
return new JsonPrimitive(((BooleanValue) value).isValue());
125+
}
126+
if (value instanceof EnumValue) {
127+
return new JsonPrimitive(((EnumValue) value).getName());
128+
}
129+
if (value instanceof FloatValue) {
130+
return new JsonPrimitive(((FloatValue) value).getValue());
131+
}
132+
if (value instanceof IntValue) {
133+
return new JsonPrimitive(((IntValue) value).getValue());
134+
}
135+
if (value instanceof NullValue) {
136+
return JsonNull.INSTANCE;
137+
}
138+
if (value instanceof StringValue) {
139+
return new JsonPrimitive(((StringValue) value).getValue());
140+
}
141+
if (value instanceof ArrayValue) {
142+
List<Value> values = ((ArrayValue) value).getValues();
143+
JsonArray jsonArray = new JsonArray(values.size());
144+
values.forEach(v -> jsonArray.add(parseJsonValue(v)));
145+
return jsonArray;
146+
}
147+
if (value instanceof ObjectValue) {
148+
final JsonObject result = new JsonObject();
149+
((ObjectValue) value).getObjectFields().forEach(objectField ->
150+
result.add(objectField.getName(), parseJsonValue(objectField.getValue())));
151+
return result;
152+
}
153+
//Should never happen, as it would mean the variable was not replaced by the parser
154+
throw new CoercingParseLiteralException("Unknown scalar AST type: " + value.getClass().getName());
155+
}
156+
157+
private static final Map<Type, GraphQLScalarType> SCALAR_MAPPING = getScalarMapping();
158+
159+
public static boolean isScalar(Type javaType) {
160+
return SCALAR_MAPPING.containsKey(javaType);
161+
}
162+
163+
public static GraphQLScalarType toGraphQLScalarType(Type javaType) {
164+
return SCALAR_MAPPING.get(javaType);
165+
}
166+
167+
private static Map<Type, GraphQLScalarType> getScalarMapping() {
168+
Map<Type, GraphQLScalarType> scalarMapping = new HashMap<>();
169+
scalarMapping.put(JsonObject.class, JsonObjectNode);
170+
scalarMapping.put(JsonElement.class, JsonAnyNode);
171+
scalarMapping.put(JsonPrimitive.class, JsonPrimitiveNode);
172+
return Collections.unmodifiableMap(scalarMapping);
173+
}
174+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.leangen.graphql.extension.common.jackson;
2+
3+
import com.fasterxml.jackson.databind.node.DecimalNode;
4+
import com.fasterxml.jackson.databind.node.NumericNode;
5+
import com.fasterxml.jackson.databind.node.ObjectNode;
6+
import com.fasterxml.jackson.databind.node.POJONode;
7+
import io.leangen.graphql.extension.SimpleModule;
8+
import io.leangen.graphql.generator.mapping.InputConverter;
9+
import io.leangen.graphql.generator.mapping.OutputConverter;
10+
import io.leangen.graphql.generator.mapping.TypeMapper;
11+
12+
import java.util.Arrays;
13+
import java.util.List;
14+
15+
public class JacksonModule implements SimpleModule {
16+
17+
private static JsonNodeAdapter jsonNodeAdapter = new JsonNodeAdapter();
18+
private static JsonArrayAdapter jsonArrayAdapter = new JsonArrayAdapter();
19+
20+
@Override
21+
public List<TypeMapper> getTypeMappers() {
22+
return Arrays.asList(jsonArrayAdapter, jsonNodeAdapter, new JacksonObjectScalarMapper());
23+
}
24+
25+
@Override
26+
public List<OutputConverter<?, ?>> getOutputConverters() {
27+
return Arrays.asList(jsonArrayAdapter, jsonNodeAdapter);
28+
}
29+
30+
@Override
31+
public List<InputConverter<?, ?>> getInputConverters() {
32+
return Arrays.asList(jsonArrayAdapter, jsonNodeAdapter);
33+
}
34+
35+
@Override
36+
public void setUp(SetupContext context) {
37+
if (!getTypeMappers().isEmpty()) {
38+
context.getSchemaGenerator().withTypeMappersPrepended(getTypeMappers().toArray(new TypeMapper[0]));
39+
}
40+
if (!getOutputConverters().isEmpty()) {
41+
context.getSchemaGenerator().withOutputConvertersPrepended(getOutputConverters().toArray(new OutputConverter[0]));
42+
}
43+
if (!getInputConverters().isEmpty()) {
44+
context.getSchemaGenerator().withInputConvertersPrepended(getInputConverters().toArray(new InputConverter[0]));
45+
}
46+
context.getSchemaGenerator().withTypeAliasGroup(ObjectNode.class, POJONode.class);
47+
context.getSchemaGenerator().withTypeAliasGroup(DecimalNode.class, NumericNode.class);
48+
}
49+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.leangen.graphql.extension.common.jackson;
2+
3+
import com.fasterxml.jackson.databind.node.POJONode;
4+
import graphql.schema.GraphQLScalarType;
5+
import io.leangen.graphql.generator.BuildContext;
6+
import io.leangen.graphql.generator.OperationMapper;
7+
import io.leangen.graphql.generator.mapping.TypeMapper;
8+
9+
import java.lang.reflect.AnnotatedType;
10+
11+
public class JacksonObjectScalarMapper implements TypeMapper {
12+
13+
@Override
14+
public GraphQLScalarType toGraphQLType(AnnotatedType javaType, OperationMapper operationMapper, BuildContext buildContext) {
15+
return JacksonObjectScalars.toGraphQLScalarType(javaType.getType());
16+
}
17+
18+
@Override
19+
public GraphQLScalarType toGraphQLInputType(AnnotatedType javaType, OperationMapper operationMapper, BuildContext buildContext) {
20+
if (POJONode.class.equals(javaType.getType())) {
21+
throw new UnsupportedOperationException(POJONode.class.getSimpleName() + " can not be used as input");
22+
}
23+
return toGraphQLType(javaType, operationMapper, buildContext);
24+
}
25+
26+
@Override
27+
public boolean supports(AnnotatedType type) {
28+
return JacksonObjectScalars.isScalar(type.getType());
29+
}
30+
}

0 commit comments

Comments
 (0)