I am building a REST application in Java that needs to dynamically build GraphQL payloads to send to a GraphQL API.
We will receive a String entityType = "MyEntity"
and a Map<String, Object> myMap
that might look like this:
{
"blobUrl": "myurl.com",
"accountId": 12345,
"user": {
"id": 4,
"name": "username"
}
}
It could have many other keys with values of different types instead, so there is no way I can create a template beforehand that indicates which variables need to be bound.
All that we know about the GraphQL payload ahead of time, is that it will be of this format:
mutation {
Create%s(%s)
}
With that information, I want to generate the following GraphQL payload:
mutation {
CreateMyEntity(
blobUrl: "myurl.com"
accountId: 12345
user: {
id: 4
name: "username"
}
)
}
There are ways I could do this by manually looping through myMap
and building the payload with a StringBuilder
, but I want to know if there is a better or pre-existing way of doing this.
I have looked all over and haven’t found anything that doesn’t involve having a template that clearly defines the variables and their types.
2
I believe you are focused on building the query string yourself.
Here is an example of custom serialization:
Entry point
This contains the main method.
package org.example.graphql;
public class Runner {
public static void main(String[] args) throws Exception {
GraphQLSerializer entitySerializer = new GraphQLMutationSerializer("CreateMyEntity");
String payload = entitySerializer.serialize(jsonInput);
System.out.printf("Formatted properly? %b%n", payload.equals(expectedGraphQL));
}
private static final String jsonInput = """
{
"blobUrl": "myurl.com",
"accountId": 12345,
"user": {
"id": 4,
"name": "username"
}
}
""";
private static final String expectedGraphQL = """
mutation {
CreateMyEntity(
blobUrl: "myurl.com"
accountId: 12345
user: {
id: 4
name: "username"
}
)
}
""".trim();
}
Interface
This is an interface for serialization.
package org.example.graphql;
import com.fasterxml.jackson.core.JsonProcessingException;
public interface GraphQLSerializer {
String serialize(String jsonInput) throws JsonProcessingException;
}
Implementation
Here is where we parse the JSON and format it.
package org.example.graphql;
import java.util.Map;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GraphQLMutationSerializer implements GraphQLSerializer {
private final ObjectMapper objectMapper = new ObjectMapper();
private final String mutationName;
private final int indentSize;
public GraphQLMutationSerializer(String mutationName, int indentSize) {
this.mutationName = mutationName;
this.indentSize = indentSize;
}
public GraphQLMutationSerializer(String mutationName) {
this(mutationName, 4);
}
@Override
@SuppressWarnings({ "unchecked" })
public String serialize(String jsonInput) throws JsonProcessingException {
// Parse the input JSON string into a Map using Jackson
Map<String, Object> inputMap = objectMapper.readValue(jsonInput, Map.class);
// Format the body and indentation
String body = formatBody(inputMap, indentSize, indentSize);
String indentation = indent(indentSize);
return String.format("mutation {n%s%s(n%sn%s)n}", indentation, mutationName, body, indentation);
}
private static String formatBody(Map<String, Object> map, int indentSize, int currentIndent) {
return map.entrySet().stream()
.map(entry -> String.format(
"%s%s: %s",
indent(currentIndent + indentSize),
entry.getKey(),
formatValue(entry.getValue(), indentSize, currentIndent + indentSize)))
.collect(Collectors.joining("n"));
}
private static String formatValue(Object value, int indentSize, int currentIndent) {
if (value instanceof String) {
return """ + value + """;
} else if (value instanceof Number || value instanceof Boolean) {
return value.toString();
} else if (value instanceof Map) {
return String.format(
"{n%sn%s}",
formatBody((Map<String, Object>) value, indentSize, currentIndent),
indent(currentIndent));
} else {
throw new IllegalArgumentException("Unsupported value type: " + value.getClass());
}
}
private static String indent(int level) {
return " ".repeat(level);
}
}