Class Serialization

java.lang.Object
com.commercetools.docs.meta.Serialization

public class Serialization extends Object
Table of content

Serialization

The SDK uses Jackson for searializing and deserializing JSON. The default configured ObjectMapper uses some modules to correctly work with our API. The details can be found in JsonUtils.createObjectMapper(ModuleOptions)

Customization

To allow customization of the ObjectMapper the SDK uses ServiceLoader for ModuleSupplier. Adding a file resources/META-INF/services/io.vrap.rmf.base.client.utils.json.ModuleSupplier to your project with FQCN of the module supplier to be used will register the supplied modules

E.g.:

io.vrap.rmf.base.client.utils.json.ModuleSupplier:
com.commercetools.api.json.ApiModuleSupplier
ApiModule.java:
package com.commercetools.api.json; import java.util.Optional; import com.commercetools.api.models.product.AttributeImpl; import com.commercetools.api.models.product_search.ProductSearchFacetResult; import com.commercetools.api.models.type.FieldContainerImpl; import com.fasterxml.jackson.databind.module.SimpleModule; import io.vrap.rmf.base.client.utils.json.modules.ModuleOptions; /** * Module to configure the default jackson {@link com.fasterxml.jackson.databind.ObjectMapper} e.g. to deserialize attributes and custom fields */ public class ApiModule extends SimpleModule { private static final long serialVersionUID = 0L; public ApiModule(ModuleOptions options) { boolean attributeAsDateString = Boolean.parseBoolean( Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_DATE_ATTRIBUTE_AS_STRING)) .orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_DATE_ATTRIBUTE_AS_STRING))); boolean customFieldAsDateString = Boolean .parseBoolean(Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_DATE_FIELD_AS_STRING)) .orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_DATE_FIELD_AS_STRING))); boolean attributeAsJsonNode = Boolean.parseBoolean( Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_ATTRIBUTE_AS_JSON_NODE)) .orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_ATTRIBUTE_AS_JSON_NODE))); boolean customFieldAsJsonNode = Boolean.parseBoolean( Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_CUSTOM_FIELD_AS_JSON_NODE)) .orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_CUSTOM_FIELD_AS_JSON_NODE))); boolean attributeNumberAsDouble = Boolean.parseBoolean( Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_ATTRIBUTE_NUMBER_AS_DOUBLE)) .orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_ATTRIBUTE_NUMBER_AS_DOUBLE))); boolean customFieldNumberAsDouble = Boolean.parseBoolean( Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_CUSTOM_FIELD_NUMBER_AS_DOUBLE)) .orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_CUSTOM_FIELD_NUMBER_AS_DOUBLE))); setMixInAnnotation(ProductSearchFacetResult.class, ProductSearchFacetResultMixin.class); if (attributeAsJsonNode) { setMixInAnnotation(AttributeImpl.class, AttributeJsonNodeMixin.class); } else { addDeserializer(AttributeImpl.class, new AttributeDeserializer(attributeAsDateString, attributeNumberAsDouble)); } if (customFieldAsJsonNode) { addDeserializer(FieldContainerImpl.class, new CustomFieldJsonNodeDeserializer()); } else { addDeserializer(FieldContainerImpl.class, new CustomFieldDeserializer(customFieldAsDateString, customFieldNumberAsDouble)); } } }

Date and time attributes

When using Date, Time and DateTime types for product attributes or custom fields the SDK deserializes them as LocalDate, LocalTime and ZonedDateTime. As sometimes it may needed to serialize them as String the ObjectMapper can be configured with ModuleOptions. The ApiModule also is loading the configuration options using System.getProperty(String) e.g.: commercetools.deserializeDateAttributeAsString

ApiModuleOptions options = ApiModuleOptions.of()
        .withDateAttributeAsString(true)
        .withDateCustomFieldAsString(true);
ObjectMapper mapper = JsonUtils.createObjectMapper(options);

ProjectApiRoot apiRoot = ApiRootBuilder.of()
        .withApiBaseUrl(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
        .withSerializer(ResponseSerializer.of(mapper))
        .build("test");

ProductVariant variant = mapper.readValue(stringFromResource("attributes.json"), ProductVariant.class);

assertThat(variant.getAttributes()).isNotEmpty();

Map<String, Attribute> attributes = variant.withProductVariant(AttributeAccessor::asMap);

assertThat(attributes.get("date").getValue()).isInstanceOfSatisfying(String.class,
    localDate -> assertThat(localDate).isEqualTo("2020-01-01"));
assertThat(attributes.get("time").getValue()).isInstanceOfSatisfying(String.class,
    localTime -> assertThat(localTime).isEqualTo("13:15:00.123"));
assertThat(attributes.get("datetime").getValue()).isInstanceOfSatisfying(String.class,
    dateTime -> assertThat(dateTime).isEqualTo("2020-01-01T13:15:00.123Z"));
assertThat(attributes.get("date").withAttribute(AttributeAccessor::asDate))
        .isInstanceOfSatisfying(LocalDate.class, localDate -> assertThat(localDate).isEqualTo("2020-01-01"));
assertThat(attributes.get("time").withAttribute(AttributeAccessor::asTime))
        .isInstanceOfSatisfying(LocalTime.class, localTime -> assertThat(localTime).isEqualTo("13:15:00.123"));
assertThat(attributes.get("datetime").withAttribute(AttributeAccessor::asDateTime)).isInstanceOfSatisfying(
    ZonedDateTime.class, dateTime -> assertThat(dateTime).isEqualTo("2020-01-01T13:15:00.123Z"));

assertThat(attributes.get("set-date").getValue()).asList().first().isInstanceOf(String.class);
assertThat(attributes.get("set-time").getValue()).asList().first().isInstanceOf(String.class);
assertThat(attributes.get("set-datetime").getValue()).asList().first().isInstanceOf(String.class);
assertThat(attributes.get("set-date").withAttribute(AttributeAccessor::asSetDate)).asList()
        .first()
        .isInstanceOf(LocalDate.class);
assertThat(attributes.get("set-time").withAttribute(AttributeAccessor::asSetTime)).asList()
        .first()
        .isInstanceOf(LocalTime.class);
assertThat(attributes.get("set-datetime").withAttribute(AttributeAccessor::asSetDateTime)).asList()
        .first()
        .isInstanceOf(ZonedDateTime.class);

See the test code.

Number attributes

When using numbers for attributes and custom fields they will be automatically deserialized as Long if they don't have a fraction part. This can be disabled with the options ApiModuleOptions.DESERIALIZE_ATTRIBUTE_NUMBER_AS_DOUBLE and ApiModuleOptions.DESERIALIZE_CUSTOM_FIELD_NUMBER_AS_DOUBLE

ApiModuleOptions options = ApiModuleOptions.of().withAttributeNumberAsDouble(true);
ObjectMapper mapper = JsonUtils.createObjectMapper(options);

ProductVariant variant = mapper.readValue(stringFromResource("attributes.json"), ProductVariant.class);

assertThat(variant.getAttribute("integer").getValue()).isEqualTo(10.0);

See the test code.

ApiModuleOptions options = ApiModuleOptions.of().withCustomFieldNumberAsDouble(true);
ObjectMapper mapper = JsonUtils.createObjectMapper(options);
CustomFields customFields = mapper.readValue(stringFromResource("customfields.json"), CustomFields.class);
assertThat(customFields.getFields().values().get("integer")).isEqualTo(10.0);

See the test code.

Deserialize Attributes as JsonNode

In case the automatic deserialization of attributes or custom fields is not needed you can set the option commercetools.deserializeAttributeAsJsonNode and/or commercetools.deserializeCustomFieldAsJsonNode

ApiModuleOptions options = ApiModuleOptions.of().withCustomFieldAsJsonNode(true).withAttributeAsJsonNode(true);
ObjectMapper mapper = JsonUtils.createObjectMapper(options);

ProjectApiRoot apiRoot = ApiRootBuilder.of()
        .withApiBaseUrl(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
        .withSerializer(ResponseSerializer.of(mapper))
        .build("test");

See the test code.

  • Constructor Details

    • Serialization

      public Serialization()