Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialization to type BaseMoney fails #498

Open
Sectan opened this issue Oct 3, 2023 · 1 comment
Open

Deserialization to type BaseMoney fails #498

Sectan opened this issue Oct 3, 2023 · 1 comment
Assignees
Labels
bug Something isn't working

Comments

@Sectan
Copy link

Sectan commented Oct 3, 2023

Describe the bug
Querying price information of a product with the graphql-api results in deserialization errors:

Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class com.commercetools.graphql.api.types.BaseMoney]: missing type id property '__typename' (for POJO property 'value')

To Reproduce

public class Reproducer {

    public static void main(String[] args) {
        final ProjectApiRoot client = createApiClient();
        Logger logger = LoggerFactory.getLogger(Reproducer.class.getName());

        GraphQLResponse<ProductQueryResult> responseEntity =
                client
                        .graphql()
                        .query(GraphQL.products(q -> q.limit(8))
                                .projection(p -> p.total().results().id().masterData().current().masterVariant().prices().value().centAmount()))
                        .executeBlocking()
                        .getBody();
        responseEntity.getData().getResults().forEach(result -> {
            logger.info("Id: " + result.getId() + "Name: " + result.getMasterData().getCurrent().getName());
            logger.info("Id: " + result.getId() + "Name: " + result.getMasterData().getCurrent().getMasterVariant().getPrices());
        });
        client.close();
    }

    public static ProjectApiRoot createApiClient() {
        var projectApiRoot = ApiRootBuilder.of()
                .defaultClient(
                        ClientCredentials.of()
                                .withClientId("<CLIENT-ID>")
                                .withClientSecret("<SECRET")
                                .build(),
                        ServiceRegion.GCP_EUROPE_WEST1.getOAuthTokenUrl(),
                        ServiceRegion.GCP_EUROPE_WEST1.getApiUrl()
                )
                .build("<PROJECT-KEY>");

        return projectApiRoot;
    }
}

Expected behavior
Reproducer code should run and price information should be deserialized without an error.

Screenshots/Code snippet
Following error is thrown:
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class com.commercetools.graphql.api.types.BaseMoney]: missing type id property '__typename' (for POJO property 'value')

Stack information (please complete the following information):

  • Java: 17
  • SDK: 16.3.0

Additional context
NA

@Sectan Sectan added the bug Something isn't working label Oct 3, 2023
@Sectan Sectan changed the title Deserialization of type BaseMoney fails Deserialization to type BaseMoney fails Oct 3, 2023
@jenschude jenschude self-assigned this Oct 5, 2023
@jenschude
Copy link
Contributor

The reason for this is that the BaseMoney is an interface and it has two implementations Money and HighPrecisionMoney. The Deserializer can't distinguish them as you didn't "cast" the request to either money or highprecision money

projectRoot
        .graphql()
        .query(GraphQL.products(q -> q.limit(8))
                .projection(p -> p.total().results().id().masterData().current().masterVariant().prices().value().onMoney().centAmount()))
        .executeBlocking()
        .getBody();

Adding onMoney() to the projection will request the __typeName field so the ObjectMapper can deserialize correctly.

Another possibility is to declare a DefaultImplementation for the ObjectMapper. This can be done by creating a Mixin class

import com.commercetools.graphql.api.types.Money;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "__typename",
        defaultImpl = Money.class // use this implementation if no subtype implementation can be found
)
public interface BaseMoneyMixin {
}

Then create Jackson module in your application:

import com.commercetools.graphql.api.types.BaseMoney;
import com.fasterxml.jackson.databind.module.SimpleModule;

public class BaseMoneyModule extends SimpleModule {
    private static final long serialVersionUID = 0L;

    public BaseMoneyModule() {
        setMixInAnnotation(BaseMoney.class, BaseMoneyMixin.class);
    }
}

And registering a service locator entry in your resources folder (resources/META-INF/services/com.fasterxml.jackson.databind.module.SimpleModule):

com.commercetools.graphql.api.BaseMoneyModule

The ObjectMapper of the SDK will load this module and register it, so that in case no projection implementation was choosen it defaults to Money.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants