Netflix / Netflix/dgs-codegen

Support Custom Serialization for Date Fields in InputValueSerializer

Open
#691 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Kotlin
Stars
217
Forks
116
PR merge metrics
No merged PRs in 30d

Description

Issue Description

The current implementation of the InputValueSerializer in the DGS Codegen library does not support custom serialization for Date fields. This limitation makes it challenging to handle Date objects properly when constructing GraphQL queries, especially when dates need to be serialized to ISO 8601 format.

Steps to Reproduce
  1. Create a GraphQL query request that includes Date fields in the input.
  2. Attempt to serialize the query using GraphQLQueryRequest.serialize().
  3. Observe that Date fields are not converted to ISO 8601 format.
Expected Behavior

The InputValueSerializer should provide a way to handle custom serialization for Date fields, allowing dates to be converted to ISO 8601 strings during the serialization process.

Current Workaround

Currently, users need to preprocess the input map manually before creating the GraphQLQueryRequest. This involves traversing the input map and converting Date objects to ISO 8601 strings using a utility method. While effective, this approach adds additional complexity and is not ideal.

Example of the Current Workaround
import com.fasterxml.jackson.databind.util.StdDateFormat;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class GraphQLUtil {

    private static final StdDateFormat dateFormat = new StdDateFormat();

    public static Map<String, Object> preprocessInput(Map<String, Object> input) {
        Map<String, Object> processedInput = new HashMap<>();
        for (Map.Entry<String, Object> entry : input.entrySet()) {
            Object value = entry.getValue();
            if (value instanceof Date) {
                processedInput.put(entry.getKey(), dateFormat.format((Date) value));
            } else {
                processedInput.put(entry.getKey(), value);
            }
        }
        return processedInput;
    }
}
import java.util.HashMap;
import java.util.Map;

public final class GraphQLQueryRequest {
    @NotNull
    private final GraphQLQuery query;
    @Nullable
    private final BaseProjectionNode projection;
    @NotNull
    private final InputValueSerializer inputValueSerializer;
    @NotNull
    private final ProjectionSerializer projectionSerializer;

    public GraphQLQueryRequest(@NotNull GraphQLQuery query, @Nullable BaseProjectionNode projection, @Nullable Map<Class<?>, ? extends Coercing<?, ?>> scalars) {
        Intrinsics.checkNotNullParameter(query, "query");
        super();
        this.query = query;
        this.projection = projection;
        this.inputValueSerializer = new InputValueSerializer(scalars != null ? scalars : MapsKt.emptyMap());
        this.projectionSerializer = new ProjectionSerializer(this.inputValueSerializer);
    }

    // Additional constructor to handle only query and projection
    public GraphQLQueryRequest(@NotNull GraphQLQuery query, @Nullable BaseProjectionNode projection) {
        this(query, projection, null);
    }

    @NotNull
    public final GraphQLQuery getQuery() {
        return this.query;
    }

    @Nullable
    public final BaseProjectionNode getProjection() {
        return this.projection;
    }

    @NotNull
    public final InputValueSerializer getInputValueSerializer() {
        return this.inputValueSerializer;
    }

    @NotNull
    public final ProjectionSerializer getProjectionSerializer() {
        return this.projectionSerializer;
    }

    @NotNull
    public final String serialize() {
        OperationDefinition.Builder operationDef = OperationDefinition.newOperationDefinition();
        String queryName = this.query.getName();
        if (queryName != null) {
            operationDef.name(queryName);
        }

        String operationType = this.query.getOperationType();
        if (operationType != null) {
            operationDef.operation(Operation.valueOf(operationType.toUpperCase(Locale.ROOT)));
        }

        if (!this.query.getVariableDefinitions().isEmpty()) {
            operationDef.variableDefinitions(this.query.getVariableDefinitions());
        }

        Field.Builder selection = Field.newField(this.query.getOperationName());
        if (!this.query.getInput().isEmpty()) {
            Map<String, Object> inputMap = this.query.getInput();
            Map<String, Object> processedInputMap = GraphQLUtil.preprocessInput(inputMap);
            List<Argument> arguments = new ArrayList<>();
            for (Map.Entry<String, Object> entry : processedInputMap.entrySet()) {
                arguments.add(new Argument(entry.getKey(), this.inputValueSerializer.toValue(entry.getValue())));
            }
            selection.arguments(arguments);
        }

        if (this.projection != null) {
            SelectionSet selectionSet = this.projection instanceof BaseSubProjectionNode
                ? this.projectionSerializer.toSelectionSet(((BaseSubProjectionNode) this.projection).root())
                : this.projectionSerializer.toSelectionSet(this.projection);
            if (!selectionSet.getSelections().isEmpty()) {
                selection.selectionSet(selectionSet);
            }
        }

        operationDef.selectionSet(SelectionSet.newSelectionSet().selection(selection.build()).build());
        return AstPrinter.printAst(operationDef.build());
    }
}
Proposed Solution

Enhance the InputValueSerializer to support custom serializers for specific types, including Date. This could be achieved by allowing users to register custom serializers for different classes, which would then be used during the serialization process.

Additional Context

Adding support for custom serializers will improve the flexibility and usability of the DGS Codegen library, making it easier to work with various data types, including Date, in a more seamless and intuitive manner.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with InputValueSerializer and GraphQLQueryRequest.serialize(); inspect how inputMap values reach toValue and compare with the workaround’s Date handling. Done means callers can register custom serializers for types such as Date and serialization uses them, with focused coverage added where the repository’s existing tests indicate.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kotlin
Domain
api, backend-api-design
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.