You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于GraphQL Schema实现响应的类型保留反序列化问询

Great question! I’ve dealt with exactly this scenario using graphql-java, and you’re right—manually tracing types through aliases, fragments, and custom scalars is a huge pain. The good news is graphql-java has built-in tools to handle this without reinventing the wheel.

Core Approach: Leverage graphql-java's TypeInfo and QueryTraversal

The framework already has logic to resolve field types, aliases, and fragments during execution, and you can reuse that same logic for deserialization. Here's how to implement it step by step:

1. First, Register Your Custom Scalar with Coercing Logic

Make sure your Date scalar is registered with a Coercing implementation that handles converting between the JSON string and LocalDate—this is the same logic you’d use for serialization, but we’ll reuse it for deserialization:

ScalarType dateScalar = ScalarType.newScalar()
    .name("Date")
    .coercing(new Coercing<LocalDate, String>() {
        @Override
        public String serialize(Object dataFetcherResult) throws CoercingSerializeException {
            return ((LocalDate) dataFetcherResult).toString();
        }

        @Override
        public LocalDate parseValue(Object input) throws CoercingParseValueException {
            return LocalDate.parse((String) input);
        }

        @Override
        public LocalDate parseLiteral(Object input) throws CoercingParseLiteralException {
            if (input instanceof StringValue) {
                return LocalDate.parse(((StringValue) input).getValue());
            }
            throw new CoercingParseLiteralException("Expected StringValue for Date scalar");
        }
    })
    .build();

// Add this scalar to your schema
GraphQLSchema schema = GraphQLSchema.newSchema()
    .additionalScalar(dateScalar)
    .query(queryType) // Your existing Query type definition
    .build();

2. Parse the Query and Traverse Its Structure

Use QueryTraversal and TypeInfo to walk through your query (including aliases and expanded fragments) and map each JSON field to its correct GraphQL type:

// 1. Prepare your inputs
String jsonResponse = "[ {\"name\": \"John Doe\", \"dayOfBirth\": \"1983-12-07\"} ]";
String query = "query { allUsers { name dayOfBirth: birthday } }";
GraphQLSchema schema = ...; // Your registered schema

// 2. Parse the query document and get the operation
Document document = Parser.parse(query);
Optional<OperationDefinition> operation = DocumentASTUtil.getOperationDefinition(document, null);
if (operation.isEmpty()) {
    throw new IllegalArgumentException("No valid operation found in query");
}

// 3. Parse the raw JSON response (using graphql-java's GraphQLResult)
GraphQLResult result = new GraphQLResult("{ \"data\": { \"allUsers\": " + jsonResponse + " } }");
Map<String, Object> data = result.getData();
List<Map<String, Object>> allUsers = (List<Map<String, Object>>) data.get("allUsers");

// 4. Use TypeInfo and QueryTraversal to resolve field types
TypeInfo typeInfo = new TypeInfo(schema);
typeInfo.reset(schema.getQueryType()); // Start at the root Query type

QueryTraversal.query(document)
    .operation(operation.get())
    .traverse(new QueryVisitorStub() {
        @Override
        public void visitField(Field field) {
            // Handle aliases: use the alias from the query if present, else the field name
            String responseFieldName = field.getAlias() != null ? field.getAlias() : field.getName();
            GraphQLFieldDefinition fieldDef = typeInfo.getFieldDef();
            GraphQLType fieldType = fieldDef.getType();

            // For list types, we need to unwrap to get the underlying element type
            GraphQLType unwrappedType = GraphQLUnmodifiedType.unwrap(fieldType);

            // If this is a custom scalar, convert the raw JSON value
            if (unwrappedType instanceof GraphQLScalarType scalarType) {
                for (Map<String, Object> user : allUsers) {
                    Object rawValue = user.get(responseFieldName);
                    Object convertedValue = scalarType.getCoercing().parseValue(rawValue);
                    user.put(responseFieldName, convertedValue);
                }
            }

            // Update TypeInfo to traverse child fields
            typeInfo.push(fieldDef.getType());
        }

        @Override
        public void leaveField(Field field) {
            typeInfo.pop();
        }
    });

// Now allUsers contains the deserialized values: dayOfBirth will be a LocalDate

3. Bonus: Map to Java DTOs (Handling Aliases)

If you want to map the processed data to a Java User class (with a birthday field, even though the query uses dayOfBirth as an alias), you can use Jackson’s @JsonProperty annotation to map the alias to the field:

public class User {
    private String name;
    @JsonProperty("dayOfBirth")
    private LocalDate birthday;

    // Getters and setters
}

Then convert the processed allUsers list to your DTOs:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
List<User> userDtos = mapper.convertValue(allUsers, new TypeReference<List<User>>() {});

Key Advantages of This Method

  • No Manual Type Tracing: QueryTraversal automatically handles aliases, inline fragments, and spread fragments, so you don’t have to parse the query AST manually.
  • Reuses Scalar Logic: The same Coercing implementation used for serialization is reused for deserialization, ensuring consistency.
  • Framework-Native: This uses graphql-java’s built-in utilities, so it’s compatible with future updates to the library.

内容的提问来源于stack exchange,提问作者Felk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 10:17:26