Handling Structured and Nested Product Data

The AttributeNestedType is a tool that can be used to model tabular, structured data for products. This feature is still in public beta and has certain caveats that you need to be aware of when deciding to use it in your project. In this tutorial we will show a sample use case for nested attributes and we will explain the potential issues.

Example use case

Imagine, you have a shop that offers food products. As required in many countries you are required to provide the nutrition facts about your food products. To do so, you need to provide quantities for, for example, sugar, fat, and protein for each of the products you offer. Look at the tub of ice cream below for which we need to store some nutrition facts.

Image Ice Cream Nutrition

One way of addressing such requirement is to have a structured data object that holds all those nutrient information and could be reused in other products. This is a use case for nested types. You can easily define ProductTypes with custom Attributes the products of that type should have in common and you can nest this ProductType into another one.

Create simple Product Type

Our objective is to construct a food-product-type that contains nutrition information captured in a ProductType nutrient-information. Moreover, the food-product-type does not only contain one object of the type nutrient-information, but a structure such as a Set of them, for example, one for fat, one for sugar, and so on as depicted in the figure below:.

Image Food ProductType

We need to start with the creation of the simple ProductType that we will then nest into the advanced ProductType. We must do it this way since we need the typeID of the ProductType to be nested first. Otherwise we wouldn't be able to setup the reference relationship between the advanced ProductType and the nested ProductType properly.

For the example we need to design the nutrient-information ProductType first. To keep it simple this ProductType contains two attributes only: one that holds a number indicating the quantity of a nutrient (quantityContained) and one attribute for the type of nutrient (nutrientTypeCode). We'll set both attributes as mandatory to be filled with values by setting isRequired to true for each attribute.

For now, nested attributes are not searchable, and as such, we need to set isSearchable to false.

{
"name": "nutrient-information",
"description": "The nutrient-information product type.",
"attributes": [
{
"name": "quantityContained",
"type": {
"name": "number"
},
"isRequired": true,
"attributeConstraint": "None",
"isSearchable": false,
"label": {
"en": "quantity contained"
}
},
{
"name": "nutrientTypeCode",
"type": {
"name": "text"
},
"isRequired": true,
"attributeConstraint": "None",
"isSearchable": false,
"label": {
"en": "nutrient type Code"
}
}
]
}

Create advanced Product Type

After we created the product type to be nested we can now create the advanced food-product-type by referencing the previously created nutrient-information product type in a nested attribute we call nutrients. Furthermore we add the attribute taste that gives a textual description about how excellent the food product tastes.

{
"name": "food-product-type",
"description": "The food product type.",
"attributes": [
{
"name": "taste",
"type": {
"name": "text"
},
"isRequired": true,
"attributeConstraint": "None",
"isSearchable": false,
"label": {
"en": "taste"
}
},
{
"name": "nutrients",
"type": {
"name": "set",
"elementType": {
"name": "nested",
"typeReference": {
"id": "<nutrient-information-product-type-id>",
"typeId": "product-type"
}
}
},
"isRequired": false,
"attributeConstraint": "None",
"isSearchable": false,
"label": {
"en": "food nutrients"
}
}
]
}

Create Product

We now have everything we need for creating an example-food-product of the food-product-type. The taste of the example-food-product is excellent of course, but we are also nesting following nutrient-information into the example-food-product: 1.4 units of FAT and 1.15 units of SUGAR. For the example we have reused the nutrient-information product type two times, but you can add more if you need since we defined the nutrients attribute of the food-product-type as set of nutrient-information.

Image Example Food Product

For doing this with the Composable Commerce API you Create a Product with the following AttributeDefinition:

{
//...
"attributes": [
{
"name": "taste",
"value": "excellent"
},
{
"name": "nutrients",
"value": [
[
{
"name": "quantityContained",
"value": 1.4
},
{
"name": "nutrientTypeCode",
"value": "FAT"
}
],
[
{
"name": "quantityContained",
"value": 1.15
},
{
"name": "nutrientTypeCode",
"value": "SUGAR"
}
]
]
}
]
//...
}

Import Product with Import API

For creating/updating an example-food-product with the commercetools Import API, you can either import a Product using Product-Draft or import a Product Variant using Product-Variant with the following AttributeDefinition:

Import a Product using Product-Draftjson
{
"type": "product-draft",
"resources": [
{
"key": "example-food-product",
"name": {
"en": "example-food-product"
},
"slug": {
"en": "example-food-product"
},
"productType": {
"typeId": "product-type",
"key": "food-product-type"
},
"masterVariant": {
"key": "example-food-product-variant1",
"attributes": [
{
"name": "taste",
"value": "excellent"
},
{
"name": "nutrients",
"value": [
[
{
"name": "quantityContained",
"value": 1.1
},
{
"name": "nutrientTypeCode",
"value": "FAT"
}
],
[
{
"name": "quantityContained",
"value": 1.5
},
{
"name": "nutrientTypeCode",
"value": "SUGAR"
}
]
]
}
]
}
}
]
}
Import a Product Variant using Product-Variantjson
{
"type": "product-variant",
"resources": [
{
"key": "example-food-product-variant1",
"sku": "example-food-product-variant1",
"product": {
"key": "example-food-product",
"typeId": "product"
},
"isMasterVariant": true,
"attributes": [
{
"name": "taste",
"value": "excellent"
},
{
"name": "nutrients",
"value": [
[
{
"name": "quantityContained",
"value": 1.2
},
{
"name": "nutrientTypeCode",
"value": "FAT"
}
],
[
{
"name": "quantityContained",
"value": 1.2
},
{
"name": "nutrientTypeCode",
"value": "SUGAR"
}
],
[
{
"name": "quantityContained",
"value": 1.2
},
{
"name": "nutrientTypeCode",
"value": "SALT"
}
]
]
}
]
}
]
}

Summary

What have we just done? Basically, we have defined a ProductType that we reused in another ProductType. This allows the composition of advanced product types based on existing types by using the AttributeNestedType.

Image Nested ProductType

The following JSON snippet shows how an existing ProductType is nested into an AttributeDefinition of another ProductType:

{
"name": "nestedProductType",
"type": {
"name": "nested",
"typeReference": {
"id": "<nested-product-type-id>",
"typeId": "product-type"
}
}
}

Caveats

  • The feature is currently in public beta meaning it can be subject to change and should be used carefully in production.
  • Values of nested type Attributes are not searchable preventing those attributes from being discoverable through the Product Projection Search API.
  • An iteration of AttributeSetType that terminates with an AttributeNestedType is limited to 5 steps.
  • Currently, there are no restrictions on nesting AttributeNestedTypes into other AttributeNestedTypes, but we cannot give any guarantees about solid query performance for such cases.
  • When setting the nested attribute values on Products, you have to provide all attribute values of the nested structure because it is not possible to update just a part of them.