question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ZoneId in Java as scalar

See original GitHub issue

Hello,

In Java we have the java.time.ZoneId object and I want to have it as scalar in GraphQL. So I have this GraphQLScararType implementation:

public class CustomZoneIdType {

    public static GraphQLScalarType getScalarType() {
        final Class<ZoneId> type = ZoneId.class;

        return new GraphQLScalarType(type.getSimpleName(), type.getSimpleName() + " for datetime",

                new Coercing<ZoneId, String>() {
                    @Override
                    public String serialize(final Object input) {
                        if (input == null) {
                            return null;
                        }
                        return input.toString();
                    }

                    @Override
                    public ZoneId parseValue(final Object input) {
                        if (input == null) {
                            return null;
                        }
                        if (input instanceof String) {
                            return ZoneId.of((String) input);
                        }
                        throw new CoercingParseValueException("Invalid input for parsing ZoneId from object");
                    }

                    @Override
                    public ZoneId parseLiteral(final Object input) {
                        if (input == null) {
                            return null;
                        }
                        if (input instanceof StringValue) {
                            return ZoneId.of(((StringValue) input).getValue());
                        }
                        throw new CoercingParseValueException("Invalid input for parsing ZoneId from literal");
                    }
                });
    }

}

Also in the GraphQLSchemaGenerator I’ve registered it like this:

            .withAdditionalTypes(Arrays.asList(
                    CustomZoneIdType.getScalarType())
            )
            .generate();

The problem is that when having the ZoneId as a field on an output it works well and the serialize method is called well. But when having it on an input object it is generated as ZoneIdInput and I cannot pass a string to it because it requires a map from me:

"errorDescription": "Could not resolve type id 'ZoneId' as a subtype of [simple type, class java.time.ZoneId]: known type ids = [] (for POJO property 'zoneId')\n at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: org.mypackage.MyClass[\"zoneId\"])"

I have io.leangen.graphql:spqr version 0.9.6.

Please help me discovering what I’m doing wrong.

Regards, Attila

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
ati90aticommented, Feb 10, 2019

I’ve adapted my code to your suggestion and it works well now. I don’t need the SCALAR_SUFFIX.

I’ll put here the final code parts, maybe if others will have the same problem this will help out them.

How to register a custom scalar The version used is 0.9.9. I have my scalar implementation:

class CustomZoneIdType {
    static GraphQLScalarType getScalarType() {
        return new GraphQLScalarType(getScalarName(), getScalarName() + " for datetime", new ZoneIdCoercing());
    }

    private static String getScalarName() {
        return ZoneId.class.getSimpleName();
    }

    private static class ZoneIdCoercing implements Coercing<ZoneId, String> {
        @Override
        public String serialize(final Object input) {
            return input.toString();
        }

        @Override
        public ZoneId parseValue(final Object input) {
            try {
                if (input instanceof String) {
                    return ZoneId.of((String) input);
                } else if (input instanceof ZoneId) {
                    return (ZoneId) input;
                } else {
                    throw Scalars.valueParsingException(input, String.class);
                }
            } catch (final Exception e) {
                throw new CoercingParseValueException(generateParseExceptionMessage(input, getScalarName()), e);
            }
        }

        @Override
        public ZoneId parseLiteral(final Object input) {
            try {
                if (input instanceof StringValue) {
                    return ZoneId.of(((StringValue) input).getValue());
                } else {
                    throw Scalars.literalParsingException(input, StringValue.class);
                }
            } catch (final Exception e) {
                throw new CoercingParseLiteralException(generateParseExceptionMessage(input, getScalarName()), e);
            }
        }
    }
}

Utility methods:

@UtilityClass
class ScalarUtils {
    private static final String EXCEPTION_MESSAGE = "Value %s could not be parsed into a %s";
    static String generateParseExceptionMessage(final Object input, final String scalarName) {
        return String.format(EXCEPTION_MESSAGE, input, scalarName);
    }
}

Then I’ve create static instance for the scalar:

@UtilityClass
public class CustomScalars {
    @SuppressWarnings(value = "ConstantName")
    public static final GraphQLScalarType GraphQLZoneId = CustomZoneIdType.getScalarType();
}

I have the type mapper like this:

public class ZoneIdMapper implements TypeMapper {
    @Override
    public GraphQLOutputType toGraphQLType(
            final AnnotatedType javaType, final OperationMapper operationMapper,
            final Set<Class<? extends TypeMapper>> mappersToSkip, final BuildContext buildContext) {
        return CustomScalars.GraphQLZoneId;
    }

    @Override
    public GraphQLInputType toGraphQLInputType(
            final AnnotatedType javaType, final OperationMapper operationMapper,
            final Set<Class<? extends TypeMapper>> mappersToSkip, final BuildContext buildContext) {
        return CustomScalars.GraphQLZoneId;
    }

    @Override
    public boolean supports(final AnnotatedType type) {
        // Apply this mapper to ZoneId and its subtypes
        return GenericTypeReflector.isSuperType(ZoneId.class, type.getType());
    }
}

And finally in the GraphQLSchemaGenerator I just added this row:

            .withTypeMappers(new ZoneIdMapper())

It seems that I don’t need to register my scalar in the withAdditionalTypes because it is working also only with calling the withTypeMappers method and registering the type mapper. Is there a reason @kaqqao to add the mapper and the GraphQLZoneId in the additional types also?

So yes, this is it with custom scalars 😄

Thank you very much @kaqqao for helping me!

0reactions
KushagraBindalcommented, May 21, 2021

Good evening Guys,

I know that this ticket is close, so can I put my comments on this ticket. Please confirm and if this is a violation then I will open a new ticket.

In my case I have applied the above logic and my ZoneId is loaded successfully. But in my case my ZoneId output should contains the rules (ZoneRules) as well which is part of ZoneOffset (extending abstract class ZoneId). But while implementing the logic it is returning id only as the outcome. Can someone please help in extracting the ZoneOffset variables i.e. Rules.

https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html https://docs.oracle.com/javase/8/docs/api/java/time/ZoneOffset.html https://docs.oracle.com/javase/8/docs/api/java/time/zone/ZoneRules.html

Please suggest.

Read more comments on GitHub >

github_iconTop Results From Across the Web

java.time.ZoneId.toString java code examples - Tabnine
ScalarTypeZoneId.formatValue(...) @Override public String formatValue(ZoneId v) { return v.toString(); }. origin: ebean-orm/ebean ...
Read more >
ZoneId (Java Platform SE 8 ) - Oracle Help Center
A ZoneId is used to identify the rules used to convert between an Instant and a LocalDateTime . There are two distinct types...
Read more >
Java Date Time - Period multipliedBy(int scalar) example
Period multipliedBy(int scalar) returns a new instance with each element in this period multiplied by the specified scalar.
Read more >
Java 8 Features - Scaler Topics
ZonedDateTime takes date and time and ZoneId as arguments. Default and Static Methods. From Java 8 onwards, interfaces can have declared methods ...
Read more >
org.cactoos.Scalar Java Examples - ProgramCreek.com
This page shows Java code examples of org.cactoos.Scalar.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found