commercetools/commercetools-sdk-java-v2

Suggestion: Easier handling for nullable attributes (and custom fields?)

pintomau opened this issue · 2 comments

Hi!

As discussed in #233 , it was suggested to use productVariant.withProductVariant(AttributeAccessor::asMap) and then a variant of attributes.get("attrName").withAttribute(AttributeAccessor::asType).

However, this makes it a bit hard to handle nullable attributes, because we'll need constant null checking or provide defaults, which makes asMap a bit limited.

Suggestion: additionally provide an abstraction (wrapping the map?) that more easily handles nullable parameters.

It should be trivial to create one on user code, but it feels like this is something the SDK should make it easier to handle? At least this feels a bit harder to work with than SDK1.

To exemplify what I'm thinking, a possible solution that eases such cases:

interface NiceAdapter {

  @Nullable
  Attribute getAttribute(String attrName);

  // doWith would only be applied if attributes[attrName] != null, otherwise return null
  // AttributeAccessor methods could be reused
  @Nullable
  default <T> T withAttributeIfPresent(String attrName, Function<Attribute, T> doWith) {
     Attribute attr = getAttribute(attrName);
     if (null == attr) return null;

    return doWith.apply(attr);
  }
}

and usage:

var attributes = productVariant.withProductVariant(AttributeAccessor::asNiceAdapter);

var attribute = attributes.withAttributeIfPresent("nullableAttribute", attr -> AttributeAccessor::asType);

// handle nullable attribute or not, if I know attr is never null

Thanks

Thanks for the suggestion.

Added an AttributesAccessor and refactored the CustomFieldsAccessor to be more null safe

https://github.com/commercetools/commercetools-sdk-java-v2/blob/main/commercetools/commercetools-sdk-java-api/src/main/java/com/commercetools/api/models/product/AttributesAccessor.java

https://github.com/commercetools/commercetools-sdk-java-v2/blob/main/commercetools/commercetools-sdk-java-api/src/main/java/com/commercetools/api/models/type/CustomFieldsAccessor.java

Usage can be seen here

public void attributesTypedAccessor() throws IOException {
ProductVariant variant = JsonUtils.fromJsonString(stringFromResource("attributes.json"), ProductVariant.class);
assertThat(variant.getAttributes()).isNotEmpty();
AttributesAccessor attributes = variant.withProductVariant(AttributesAccessor::of);
assertThat(attributes.asString("null")).isNull();
assertThat(attributes.asString("text")).isInstanceOfSatisfying(String.class,
s -> assertThat(s).isEqualTo("foo"));

public void fieldsAccessor() throws IOException {
ObjectMapper mapper = JsonUtils.createObjectMapper();
CustomFields customFields = mapper.readValue(stringFromResource("customfields.json"), CustomFields.class);
assertThat(customFields.getFields().values()).isNotEmpty();
CustomFieldsAccessor fields = customFields.withCustomFields(CustomFieldsAccessor::new);
assertThat(fields.asString("null")).isNull();
assertThat(fields.asString("text")).isInstanceOfSatisfying(String.class, s -> assertThat(s).isEqualTo("foo"));
assertThat(fields.asLocalizedString("ltext")).isInstanceOfSatisfying(LocalizedString.class,
localizedString -> assertThat(localizedString.values().get("en")).isEqualTo("foo"));

Thanks. I'll take a look asap.