Class Serialization
- Serialization
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 Summary
-
Method Summary
-
Constructor Details
-
Serialization
public Serialization()
-