Bloomreach Discovery Schema Mapping

Introduction

This document explains how the response data from Bloomreach Discovery is mapped to the GraphQL Schema models served by the GraphQL Commerce API.

For notation purposes, JSONPath expressions are used in this document to show how JSON objects and properties are resolved and mapped from the response data.

GraphQL Schema Models Overview

As explained in GraphQL Commerce Schema page, you can browse and explore the GraphQL Commerce Schema through the Playground UI or the HTML documents directly, but let's have a quick look at the core model schema snippets here.

In product searches through the Product Search API, each product item data is represented by the Item GraphQL Schema model type, each product variant item by ItemVariant, and the whole result set with multiple product items by ItemsPageResult as shown below. 

"Abstraction for both product item and product item variant"
  interface ItemLike {
    "The ItemId of a product item or product item variant"
    itemId: ItemId!
    "The display name of a product item or product item variant"
    displayName: String
    "The description of a product item or product item variant"
    description: String
    "The ImageSet of a product item or product item variant"
    imageSet: ImageSet
    "The listed price of a product item or product item variant"
    listPrice: Price
    "The purchase price of a product item or product item variant"
    purchasePrice: Price
    // ...SNIP...
  }

  "Product item abstraction"
  type Item implements ItemLike @key(fields: "itemId") {
    "The ItemId of a product item"
    itemId: ItemId!
    "The display name of a product item"
    displayName: String
    "The description of a product item"
    description: String
    "The ImageSet of a product item"
    imageSet: ImageSet
    "The listed price of a product item"
    listPrice: Price
    "The purchase price of a product item"
    purchasePrice: Price
    "Sale price range including the lowest and highest prices to put in the appropriate location"
    salePriceRange: [Float]
    "Price range including the lowest and highest prices to put in the appropriate location"
    priceRange: [Float]
    "All available product item variant dimension names. e.g, [{name:'color'}, {name:'size'}]"
    varAttrTypes: [AttributeType]
    "Product item variants of this product item"
    variants: [ItemVariant]
    // ...SNIP...
  }

  """
  Item identifier that may hold multiple keys.
  e.g, the Commerce Backend Platform specific primary key and other alternate key(s)
  such as SKU, UPC, article number, etc.
  """
  type ItemId {
    "The primary key identifier of a product item maintained by the Commerce Backend Platform"
    id: String!
    "The default alternate key identifier of a product item"
    code: String
  }

  "Product item variant abstraction"
  type ItemVariant implements ItemLike {
    "The ItemId of a product item variant"
    itemId: ItemId!
    "The display name of a product item variant"
    displayName: String
    "The description of a product item variant"
    description: String
    "Flag whether or not this is the master variant"
    master: Boolean
    "The ImageSet of a product item variant"
    imageSet: ImageSet
    "The listed price of a product item variant"
    listPrice: Price
    "The purchase price of a product item variant"
    purchasePrice: Price
    "The main product item which contains this product item variant"
    mainItem: Item
    "Product item variant dimension name-values mappings"
    varAttrs: [Attribute]
    // ...SNIP...
  }

  "Pagination-based product search result"
  type ItemsPageResult implements PageResult {
    "The offset index from which the search result starts"
    offset: Int!
    "The maximum size limit of the search result"
    limit: Int!
    "The item count of the search result"
    count: Int!
    "The total item count of the search result"
    total: Int!
    "The list of product items of the search result"
    items: [Item]!
    "Optional facet result available in this search"
    facetResult: FacetResult
    "Additional query info"
    queryHint: QueryHint
    // ...SNIP...
  }

And product category data is represented by the following type:

"Product category abstraction"
  type Category {
    "The identifier of this category"
    id: String!
    "The identifier of the parent category of this category"
    parentId: String!
    "The display name of this category"
    displayName: String!
    """
    The optional category path to this category, delimited by '/'.
    Depending on the backend, some category items may appear in multiple tree nodes,
    in which case, this path property can be preferred to the id-parentId pair when constructing category tree nodes.
    """
    path: String
  }

Also, response data from the Autosuggest API is represented by the following:

"Suggestion search result abstraction, including term suggestions and product item suggestions"
  type SuggestionResult {
    "Term suggestions"
    terms: [String]!
    "Product item suggestions"
    items: [Item]
  }

Product Data Mapping

The response data from the Product search API looks like the following example:

{
  "response":{
    "numFound":86,
    "start":0,
    "docs":[
      {
        "sale_price":115.9,
        "price":115.9,
        "score":0.014999093,
        "description":"",
        "title":"PILOT SPORT A/S 3+",
        "url":"",
        "brand":"Michelin",
        "pid":"PILOT_SPORT_AS3PLUS",
        "thumb_image":"https://demo-images.s3-us-west-2.amazonaws.com/VESTRI\_VIRTUAL_V2/92861.png",
        "sale_price_range":[
          115.9,
          115.9
        ],
        "price_range":[
          115.9,
          115.9
        ],
        "variants":[
          {
            "sku\_swatch\_images":[
              "92861"
            ],
            "sku_thumb_images":[
              "https://demo-images.s3-us-west-2.amazonaws.com/VESTRI_VIRTUAL_V2/92861.png"
            ]
          },
          {
            "sku_swatch_images":[
              "56201"
            ],
            "sku_thumb_images":[
              "https://demo-images.s3-us-west-2.amazonaws.com/VESTRI_VIRTUAL_V2/56201.png"
            ]
          },
          {
            "sku_swatch_images":[
              "80472"
            ],
            "sku_thumb_images":[
              "https://demo-images.s3-us-west-2.amazonaws.com/VESTRI_VIRTUAL_V2/80472.png"
            ]
          }
        ]
      },
    // ...SNIP...
    ]
  },
  "facet_counts":{
    "facet_ranges":{

    },
    "facet_fields":{
      "category":[
        {
          "count":15,
          "crumb":"/VPA_VEHICLE_ADDONS",
          "cat_name":"Vehicle Addons",
          "parent":"",
          "cat_id":"VPA_VEHICLE_ADDONS",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons"
        },
        {
          "count":14,
          "crumb":"/VPA_VEHICLE_ADDONS/VPA_VA_MCLASS",
          "cat_name":"M-Class",
          "parent":"VPA_VEHICLE_ADDONS",
          "cat_id":"VPA_VA_MCLASS",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons/VPA_VA_MCLASS,M-Class"
        },
        {
          "count":10,
          "crumb":"/VPA_VEHICLE_ADDONS/VPA_VA_T50",
          "cat_name":"T50",
          "parent":"VPA_VEHICLE_ADDONS",
          "cat_id":"VPA_VA_T50",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons/VPA_VA_T50,T50"
        },
        {
          "count":28,
          "crumb":"/VESTRI_BM_APPAREL",
          "cat_name":"Apparel",
          "parent":"",
          "cat_id":"VESTRI_BM_APPAREL",
          "tree_path":"/VESTRI_BM_APPAREL,Apparel"
        },
        {
          "count":9,
          "crumb":"/VPA_VEHICLE_ADDONS/VPA_VA_XCLASS",
          "cat_name":"X-Class",
          "parent":"VPA_VEHICLE_ADDONS",
          "cat_id":"VPA_VA_XCLASS",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons/VPA_VA_XCLASS,X-Class"
        },
        // ...SNIP...
      ],
      "brand":[
        {
          "count":80,
          "name":"Vestri"
        },
        // ...SNIP...
      ],
      "colors":[
        {
          "count":18,
          "name":"black"
        },
        {
          "count":19,
          "name":"blue"
        },
        // ...SNIP...
      ],
      // ...SNIP...
    },
    // ...SNIP...
  },
  // ...SNIP...
}

When finding multiple product items data (e.g., GraphQL queries such as findItemsByKeyword, findItemsByCategory, etc.), the primary response data object is of type ItemsPageResult, being mapped as follows:

Mapping for ItemsPageResult

Field of ItemsPageResultResolving JSONPath expression from responseNotes
offset$.response.start
limitSet to the limit query input parameter.
count($.response.docs).length
total$.response.numFound
itemsSee the Mapping for Item table below.
facetResult$.facet_count.facet_fieldsConstructs facet navigation information as FacetResult schema type, using the resolved facet fields.
queryHint{
  "autoCorrectQuery": $.autoCorrectQuery,
  "autoCorrectQuerySet": $.did_you_mean,
  "redirectHint": {
    "url": keywordRedirect['redirected url'],
    "query": keywordRedirect['original query'],
    "newQuery": keywordRedirect['redirected query']
  }
}
See the following pages for detail:

Search Query Processing: Autocorrect and Query Relaxation
Keyword redirects

Each product item data in the $.response.docs is mapped to an Item model in ItemsPageResult.items array as follows:

Mapping for Item

Field of ItemJSONPath on $.response.docs[*]Notes
itemId{
  "id": pid,
  "code": variants[0].sku_swatch_images[0]
}
itemId field consists of id and code properties, each of which is extracted from the corresponding JSONPath expression.
If the code property is not resolved, the pid value for the id field is used for the property, too.
displayNametitle
descriptiondescription
imageSet{
  "original": {
    "link": thumb_image
  },
  "thumbnail": {
    "link": thumb_image
  } 
}
listPriceprice
purchasePricesale_price
salePriceRangesale_price_range
priceRangeprice_range
variantsvariantsSee the Mapping for ItemVariant table below for details.
customAttrsSee the answer to the question, "How can I include extra custom fields from the brSM API response?" in the FAQ page.

Mapping for ItemVariant

Field of ItemVariantJSONPath on $.response.docs[*].variants[*]Notes
itemId{
"id": pid,
"code": variants[0].sku_swatch_images[0]
}
itemId field consists of id and code properties, each of which is extracted from the corresponding JSONPath expression.
If the code property is not resolved, the pid value for the id field is used for the property, too.
displayNameSame as the parent product Item's displayName property.
descriptionSame as the parent product Item's description property.
imageSet{
"original": {
"link": sku_thumb_images[0]
},
"thumbnail": {
"link": sku_thumb_images[0]
}
}
listPriceSame as the parent product Item's listPrice property.
purchasePriceSame as the parent product Item's purchasePrice property.
mainItemThe parent product Item which has all variants.
customAttrsSee the answer to the question, "How can I include extra custom fields from the brSM API response?" in the FAQ page.

Category Data Mapping

When finding categories (i.e., findCategories GraphQL query), the primary response data object is an array of Category models. For a single category finding (i.e., findCategoryByID GraphQL query), it is a single Category model if found.

Internally the queries use the same Product search API, and the response looks like the following:

{
  "response":{
    "numFound":86,
    "start":1,
    "docs":[
      // ...SNIP...
    ]
  },
  "facet_counts":{
    "facet_ranges":{

    },
    "facet_fields":{
      "category":[
        {
          "count":15,
          "crumb":"/VPA_VEHICLE_ADDONS",
          "cat_name":"Vehicle Addons",
          "parent":"",
          "cat_id":"VPA_VEHICLE_ADDONS",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons"
        },
        {
          "count":14,
          "crumb":"/VPA_VEHICLE_ADDONS/VPA_VA_MCLASS",
          "cat_name":"M-Class",
          "parent":"VPA_VEHICLE_ADDONS",
          "cat_id":"VPA_VA_MCLASS",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons/VPA_VA_MCLASS,M-Class"
        },
        {
          "count":10,
          "crumb":"/VPA_VEHICLE_ADDONS/VPA_VA_T50",
          "cat_name":"T50",
          "parent":"VPA_VEHICLE_ADDONS",
          "cat_id":"VPA_VA_T50",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons/VPA_VA_T50,T50"
        },
        {
          "count":28,
          "crumb":"/VESTRI_BM_APPAREL",
          "cat_name":"Apparel",
          "parent":"",
          "cat_id":"VESTRI_BM_APPAREL",
          "tree_path":"/VESTRI_BM_APPAREL,Apparel"
        },
        {
          "count":9,
          "crumb":"/VPA_VEHICLE_ADDONS/VPA_VA_XCLASS",
          "cat_name":"X-Class",
          "parent":"VPA_VEHICLE_ADDONS",
          "cat_id":"VPA_VA_XCLASS",
          "tree_path":"/VPA_VEHICLE_ADDONS,Vehicle Addons/VPA_VA_XCLASS,X-Class"
        },
        // ...SNIP...
      ],
      // ...SNIP...
    },
    // ...SNIP...
  },
  // ...SNIP...
}

Each category item data in the $.response.facet_counts.facet_fields.category array is mapped to a Category model like the following:

Mapping for Category

Field of CategoryJSONPath on $.response.facet_counts.facet_fields.category[*]Notes
idcat_id
parentIdparent
displayNamecat_name
pathtree_path

Autosuggest Data Mapping

The response data from the Autosuggest API looks like the following example:

{
  "queryContext":{
    "originalQuery":"b"
  },
  "suggestionGroups":[
    {
      "catalogName":"example_com",
      "view":"default",
      "querySuggestions":[
        {
          "query":"boots",
          "displayText":"boots"
        },
        {
          "query":"backpack",
          "displayText":"backpack"
        },
        {
          "query":"bench",
          "displayText":"bench"
        },
        // ...SNIP...
      ],
      "searchSuggestions":[
        {
          "sale_price":40,
          "url":"https://www.example.com/not-available/craftsman-safety-boots-size-8/5055160047421_BQ.prd?_br_psugg_q=boots",
          "pid":"5055160047421_BQ",
          "thumb_image":"//king.scene7.com/is/image/King/productTemplate?$baseImage=King/5055160047421_01bq",
          "title":"Craftsman Safety boots, Size 8"
        },
        {
          "sale_price":25,
          "url":"https://www.example.com/not-available/site-granite-grey-trainer-boots-size-10/289361_BQ.prd?_br_psugg_q=boots",
          "pid":"289361_BQ",
          "thumb_image":"//king.scene7.com/is/image/King/productTemplate?$baseImage=King/5055338404995_01bq",
          "title":"Site Granite Grey Trainer boots, Size 10"
        },
        {
          "sale_price":40,
          "url":"https://www.example.com/not-available/site-brown-mudguard-dealer-boots-size-8/3663602605942_BQ.prd?_br_psugg_q=boots",
          "pid":"3663602605942_BQ",
          "thumb_image":"//king.scene7.com/is/image/King/productTemplate?$baseImage=King/3663602605942_01bq",
          "title":"Site Brown Mudguard Dealer boots, Size 8"
        },
        // ...SNIP...
      ],
      // ...SNIP...
    }
  ]
}

When looking up suggestions (i.e., findSuggestions GraphQL query), the primary response data object is of type SuggestionResult, being mapped as follows:

Mapping for SuggestionResult

Field of SuggestionResultResolving JSONPath expression from responseNotes
terms
items$.suggestionGroups[*].querySuggestions[*].query
items$.suggestionGroups[*].searchSuggestions[*]See the Mapping for Item table above for detail on how each product item is mapped.