Create suffix attributes
In the following product dataset, theproduct_reference attribute has a numeric prefix, used for internal purposes.
JSON
[
{
"name": "Apple iPhone 16",
"product_reference": "002ABCDEF"
},
{
"name": "Apple iPhone 16 Plus",
"product_reference": "001GHIJKL"
}
]
product_reference, as an array.
namespace Algolia;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using Algolia.Search.Clients;
using Algolia.Search.Http;
using Algolia.Search.Models.Search;
class SaveObjectsModified
{
async Task Main(string[] args)
{
var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));
var jsonContent = await File.ReadAllTextAsync("products.json");
var products = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(jsonContent);
var records = products
?.Select(product =>
{
var reference = product.GetValueOrDefault("product_reference", "") as string;
var suffixes = new List<string>();
for (var i = reference!.Length; i > 1; i--)
{
suffixes.Add(reference[i..]);
}
return new Dictionary<string, object>(product) { ["suffixes"] = suffixes };
})
.ToList();
await client.SaveObjectsAsync("INDEX_NAME", records);
}
}
import 'dart:convert';
import 'dart:io';
import 'package:algolia_client_search/algolia_client_search.dart';
void saveObjectsModified() async {
final client =
SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');
final path = '/tmp/records.json';
final json = File(path).readAsStringSync();
List<Map<String, dynamic>> products;
try {
products = List<Map<String, dynamic>>.from(jsonDecode(json));
} catch (e) {
throw Exception('Failed to read file at $path: $e');
}
final records = products.map((product) {
final reference = product['product_reference'].toString();
final suffixes = [
for (int i = 1; i <= reference.length; i++)
reference.substring(i - 1, reference.length)
];
final updatedProduct = {
...product,
'product_reference_suffixes': suffixes,
};
return updatedProduct;
}).toList();
final batchParams = BatchWriteParams(
requests: records
.map((record) => BatchRequest(
action: Action.addObject,
body: record,
))
.toList());
await client.batch(
indexName: "INDEX_NAME",
batchWriteParams: batchParams,
);
}
package main
import (
"encoding/json"
"os"
"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
)
func saveObjectsModified() {
client, err := search.NewClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
if err != nil {
// The client can fail to initialize if you pass an invalid parameter.
panic(err)
}
content, err := os.ReadFile("products.json")
if err != nil {
panic(err)
}
var products []map[string]any
err = json.Unmarshal(content, &products)
if err != nil {
panic(err)
}
records := make([]map[string]any, 0, len(products))
for _, product := range products {
reference := product["product_reference"].(string)
suffixes := make([]string, 0, len(reference)-1)
for i := len(reference); i > 1; i-- {
suffixes = append(suffixes, reference[i:])
}
record := product
record["product_reference_suffixes"] = suffixes
records = append(records, record)
}
_, err = client.SaveObjects(
"INDEX_NAME", records)
if err != nil {
panic(err)
}
}
package com.algolia;
import com.algolia.api.SearchClient;
import com.algolia.config.*;
import com.algolia.model.search.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class saveObjectsModified {
public static void main(String[] args) throws Exception {
try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
JsonNode content = new ObjectMapper().readTree(new File("products.json"));
List<Map<String, Object>> products = new ObjectMapper().readerForListOf(Map.class).readValue(content);
List<Map<String, Object>> records = products
.stream()
.map(product -> {
String reference = (String) product.get("product_reference");
List<String> suffixes = new ArrayList<>();
for (int i = reference.length(); i > 1; i--) {
suffixes.add(reference.substring(i));
}
Map<String, Object> record = new HashMap<>(Map.copyOf(product));
record.put("product_reference_suffixes", suffixes);
return record;
})
.toList();
client.saveObjects("INDEX_NAME", records);
} catch (Exception e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
}
import { algoliasearch } from 'algoliasearch';
const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');
// @ts-ignore
const { default: products } = await import('./products.json');
const records = products.map((product) => {
const reference = product['product_reference'];
const suffixes: string[] = [];
for (let i = reference.length; i > 1; i--) {
suffixes.unshift(reference.slice(i));
}
return { ...product, product_reference_suffixes: suffixes };
});
await client.saveObjects({ indexName: 'indexName', objects: records });
import com.algolia.client.api.SearchClient
import com.algolia.client.configuration.*
import com.algolia.client.extensions.*
import com.algolia.client.model.search.*
import com.algolia.client.transport.*
import java.io.File
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.*
suspend fun saveObjectsModified() {
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
val path = "/tmp/records.json"
val json =
try {
File(path).readText()
} catch (e: Exception) {
throw RuntimeException("Failed to read file at $path", e)
}
val products: List<JsonObject> =
Json.decodeFromString(ListSerializer(JsonObject.serializer()), json)
val records =
products.map { product ->
val reference = product["product_reference"].toString()
val suffixes =
reference.windowed(reference.length, 1, partialWindows = true).drop(1).map {
JsonPrimitive(it)
}
JsonObject(product + ("product_reference_suffixes" to JsonArray(suffixes)))
}
client.saveObjects(indexName = "INDEX_NAME", objects = records)
}
<?php
require __DIR__.'/../vendor/autoload.php';
use Algolia\AlgoliaSearch\Api\SearchClient;
$client = SearchClient::create('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');
$path = dirname(__FILE__).DIRECTORY_SEPARATOR.'products.json';
if (!file_exists($path)) {
throw new Exception("File not found: {$path}");
}
$data = file_get_contents($path);
if (false === $data) {
throw new Exception("Failed to read file: {$path}");
}
$products = json_decode($data, true);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new Exception('JSON decode error: '.json_last_error_msg());
}
$records = array_map(function ($product) {
$reference = $product['product_reference'];
$suffixes = [];
while (strlen($reference) > 1) {
$reference = substr($reference, 1);
$suffixes[] = $reference;
}
$product['product_reference_suffixes'] = $suffixes;
return $product;
}, $products);
$client->saveObjects(
'INDEX_NAME',
$records,
);
import json
from algoliasearch.search.client import SearchClientSync
with open("products.json", "r", encoding="utf-8") as f:
products = json.load(f)
_client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
records = []
for product in products:
reference = product["product_reference"]
suffixes = []
while len(reference) > 1:
reference = reference[1:]
suffixes.append(reference)
product["product_reference_suffixes"] = suffixes
records.append(product)
_client.save_objects(
index_name="INDEX_NAME",
objects=records,
)
require "json"
require "algolia"
client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
products = JSON.parse(File.read("products.json"))
records = []
products.each do |product|
reference = product["product_reference"]
suffixes = []
while reference.length > 1
reference = reference[1..-1]
suffixes << reference
end
product["product_reference_suffixes"] = suffixes
records << product
end
client.save_objects("INDEX_NAME", records)
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.io.Source
import scala.util.Using
import org.json4s.native.JsonMethods
import org.json4s.jvalue2extractable
import algoliasearch.api.SearchClient
import algoliasearch.config.*
import algoliasearch.extension.SearchClientExtensions
import algoliasearch.search.JsonSupport
def saveObjectsModified(): Future[Unit] = {
implicit val formats: org.json4s.Formats = JsonSupport.format
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
val path = "/tmp/records.json"
val result = Using(Source.fromFile(path))(_.mkString).getOrElse {
throw new RuntimeException(s"Failed to read file at $path")
}
val products = JsonMethods.parse(result).extract[Seq[Map[String, Any]]]
val records = products.map { product =>
val reference = product("product_reference").toString
val suffixes = reference.tails.toList.drop(1).filter(_.nonEmpty)
product + ("product_reference_suffixes" -> suffixes)
}
Await.result(
client
.saveObjects(
indexName = "INDEX_NAME",
objects = records
)
.map(_ => Future.unit),
Duration(5, "seconds")
)
}
import Foundation
#if os(Linux) // For linux interop
import FoundationNetworking
#endif
import AlgoliaCore
import AlgoliaSearch
func saveObjectsModified() async throws {
let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")
let path = URL(string: #file)!.deletingLastPathComponent()
.appendingPathComponent("products.json")
let data = try Data(contentsOf: URL(fileURLWithPath: path.absoluteString))
let products = try JSONDecoder().decode([[String: AnyCodable]].self, from: data)
let records = products.map { product -> [String: AnyCodable] in
var reference = product["product_reference"]?.value as! String
var suffixes: [String] = []
while reference.count > 1 {
reference = String(reference.dropFirst())
suffixes.append(reference)
}
var record: [String: AnyCodable] = product
record["product_reference_suffixes"] = AnyCodable(suffixes)
return record
}
try await client.saveObjects(indexName: "INDEX_NAME", objects: records)
}
product_reference_suffixes:
JSON
[
{
"name": "Apple iPhone 16",
"product_reference": "002ABCDEF",
"product_reference_suffixes": ["02ABCDEF", "2ABCDEF", "ABCDEF", "BCDEF", "CDEF", "DEF", "EF", "F"]
},
{
"name": "Apple iPhone 16 Plus",
"product_reference": "001GHIJKL",
"product_reference_suffixes": ["01GHIJKL", "1GHIJKL", "GHIJKL", "HIJKL", "IJKL", "JKL", "KL", "L"]
}
]
Limitations
Generating a list of suffixes for each record can increase index size and slow performance, It may also reduce relevance for short suffixes such as “EF” or “F”, because these can match unrelated records with similar character sequences. Use this approach only if you genuinely need to query in the middle or the end of words. To reduce the impact of suffix generation, consider the following strategies:- Generate suffixes only for strings longer than a defined length, such as 4 or more characters.
- Limit suffix generation to a specific number per record, such as 5.
- Apply suffix generation only to structured fields, such as product codes or identifiers.
Update your searchable attributes
Addproduct_reference_suffixes to your searchable attributes to make the suffixes searchable.
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List<string>
{
"name",
"product_reference",
"product_reference_suffixes",
},
}
);
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"name",
"product_reference",
"product_reference_suffixes",
],
),
);
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"name", "product_reference", "product_reference_suffixes"})))
if err != nil {
// handle the eventual error
panic(err)
}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setSearchableAttributes(Arrays.asList("name", "product_reference", "product_reference_suffixes"))
);
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { searchableAttributes: ['name', 'product_reference', 'product_reference_suffixes'] },
});
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(
searchableAttributes = listOf("name", "product_reference", "product_reference_suffixes")
),
)
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'name',
'product_reference',
'product_reference_suffixes',
],
],
);
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"name",
"product_reference",
"product_reference_suffixes",
],
},
)
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(
searchable_attributes: ["name", "product_reference", "product_reference_suffixes"]
)
)
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("name", "product_reference", "product_reference_suffixes"))
)
),
Duration(100, "sec")
)
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(searchableAttributes: [
"name",
"product_reference",
"product_reference_suffixes",
])
)
product_reference_suffixes below product_reference to exact matches rank higher than partial matches.