Search indexes support the JSON field type. Map a String column in a data table to a JSON field in a search index to perform structured searches and analytics on semi-structured data. The JSON field type provides two storage modes: Object and Nested, which are suited for independent field queries and correlated nested object queries respectively.
How it works
When you map a String column to a JSON field in a search index, Tablestore parses the JSON structure and indexes individual sub-fields for querying. Two storage modes are available — Object and Nested — each optimized for different query patterns.
Object: Flattens nested structures into top-level fields. Best for simple field queries that require high performance.
Nested: Preserves the independence and field associations of each nested object. Best for queries that require exact matching within a single nested object.
Key differences
|
Dimension |
Object |
Nested |
|
Data processing |
Flattens nested data into top-level fields |
Stores each nested object as an independent document |
|
Query method |
Standard queries |
NestedQuery required |
|
Field correlation |
Allows cross-matching between fields of different nested objects |
Matches fields only within the same nested object |
|
Performance |
Lower resource consumption |
Higher resource consumption; supports correlated field queries |
Selection guidelines
Choose Nested when queries must strictly preserve field correlations within nested objects. Choose Object when high query performance is the priority and strict field correlation is not required.
Configure and use JSON field indexes
Create a search index for JSON fields in three steps: select a JSON type, configure the index, and run queries.
Whether you choose Object or Nested, explicitly define the field type for each sub-field. Sub-fields without a defined type are ignored by the index and cannot participate in queries.
Step 1: Select the JSON type and data format
The following sections compare Object and Nested types, show supported data formats, and demonstrate how to write JSON data.
JSON type selection
Object: Best for independent field queries. Provides high query performance with low resource consumption.
Nested: Best for queries that require field correlation. Guarantees accurate results within each nested object.
Hybrid: Object and Nested types can be combined in the same index to meet complex requirements.
Data format
JSON fields support both array and non-array formats. Select the format based on your data structure:
// Array format
[{ "country": "China", "city": "Hangzhou" }, { "country": "USA", "city": "Seattle" }]
// Non-array format
{ "country": "China", "city": "Hangzhou" }
Write data
private static void putRow(SyncClient client) {
// Construct the primary key.
PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
primaryKeyBuilder.addPrimaryKeyColumn("id", PrimaryKeyValue.fromString("10001"));
PrimaryKey primaryKey = primaryKeyBuilder.build();
// Specify the table name.
RowPutChange rowPutChange = new RowPutChange("<TABLE_NAME>", primaryKey);
// Build the raw JSON data.
List<Map<String, Object>> addresses = Arrays.asList(
new HashMap<String, Object>() {{ put("country", "China"); put("city", "Hangzhou"); }},
new HashMap<String, Object>() {{ put("country", "USA"); put("city", "Seattle"); }}
);
String jsonString = JSON.toJSONString(addresses);
rowPutChange.addColumn(new Column("address", ColumnValue.fromString(jsonString)));
client.putRow(new PutRowRequest(rowPutChange));
}
Step 2: Configure the field structure and create an index
The following example configures a single-level JSON field with two sub-fields. Define the field type and properties for each sub-field to make them queryable.
List<FieldSchema> subFieldSchemas = new ArrayList<FieldSchema>();
subFieldSchemas.add(new FieldSchema("country", FieldType.KEYWORD)
.setIndex(true).setEnableSortAndAgg(true));
subFieldSchemas.add(new FieldSchema("city", FieldType.KEYWORD)
.setIndex(true).setEnableSortAndAgg(true));
FieldSchema jsonFieldSchema = new FieldSchema("address", FieldType.Json)
.setJsonType(JsonType.OBJECT) // Set to JsonType.OBJECT or JsonType.NESTED
.setSubFieldSchemas(subFieldSchemas);
Step 3: Query data
Object: Flattens nested data. Access fields by concatenating parent and child field names with a period (
.). Because fields are flattened, values from different nested objects can cross-match.Nested: Preserves the independence of each nested object. Wrap query conditions in a NestedQuery to ensure field matching occurs only within the same nested object.
Indexed format
Assume the address field contains the following data: [{ "country": "China", "city": "Hangzhou" }, { "country": "USA", "city": "Seattle" }].
Object indexed format:
{"address.country": ["China", "USA"], "address.city": ["Hangzhou","Seattle"]}Nested indexed format: Independent documents
{ "country": "China", "city": "Hangzhou" }and{ "country": "USA", "city": "Seattle" }
With conditions address.country ="China" AND address.city="Seattle": Object matches (cross-object field values combine) but Nested does not (no single object has both). With conditions address.country ="China" AND address.city="Hangzhou": both types match.
Query examples
JSON Nested type query example
The following example queries rows where the same nested object of the `address` field satisfies two conditions: address.country is "China" and address.city is "Seattle".
public static void nestedQuery(SyncClient client) {
// Condition 1: The value of the country field in the address sub-row must be "China".
TermQuery termQuery1 = new TermQuery();
termQuery1.setFieldName("address.country");
termQuery1.setTerm(ColumnValue.fromString("China"));
// Condition 2: The value of the city field in the address sub-row must be "Seattle".
TermQuery termQuery2 = new TermQuery();
termQuery2.setFieldName("address.city");
termQuery2.setTerm(ColumnValue.fromString("Seattle"));
// Use the AND condition of BoolQuery to query for sub-rows that meet both conditions.
List<Query> mustQueries = new ArrayList<>();
mustQueries.add(termQuery1);
mustQueries.add(termQuery2);
BoolQuery boolQuery = new BoolQuery();
boolQuery.setMustQueries(mustQueries);
// Set BoolQuery within NestedQuery to require a sub-row to meet multiple query conditions at the same time.
NestedQuery nestedQuery = new NestedQuery(); // Set the query type to NestedQuery.
nestedQuery.setPath("address"); // Set the path of the nested type column, which is the parent path of the field to query.
nestedQuery.setQuery(boolQuery);
nestedQuery.setScoreMode(ScoreMode.None);
SearchQuery searchQuery = new SearchQuery();
searchQuery.setQuery(nestedQuery);
SearchRequest searchRequest = new SearchRequest("<TABLE_NAME>", "<SEARCH_INDEX_NAME>", searchQuery);
SearchResponse resp = client.search(searchRequest);
System.out.println("Row: " + resp.getRows());
}
JSON Object type query example
The following example queries rows where the `address` field satisfies two conditions across its nested objects: address.country is "China" and address.city is "Seattle".
public static void boolQuery(SyncClient client) {
// Condition 1: The value of the country field in the address sub-row must be "China".
TermQuery termQuery1 = new TermQuery();
termQuery1.setFieldName("address.country");
termQuery1.setTerm(ColumnValue.fromString("China"));
// Condition 2: The value of the city field in the address sub-row must be "Seattle".
TermQuery termQuery2 = new TermQuery();
termQuery2.setFieldName("address.city");
termQuery2.setTerm(ColumnValue.fromString("Seattle"));
// Use the AND condition of BoolQuery to query for sub-rows that meet both conditions.
List<Query> mustQueries = new ArrayList<>();
mustQueries.add(termQuery1);
mustQueries.add(termQuery2);
BoolQuery boolQuery = new BoolQuery();
boolQuery.setMustQueries(mustQueries);
SearchQuery searchQuery = new SearchQuery();
searchQuery.setQuery(boolQuery);
SearchRequest searchRequest = new SearchRequest("<TABLE_NAME>", "<SEARCH_INDEX_NAME>", searchQuery);
SearchResponse resp = client.search(searchRequest);
System.out.println("Row: " + resp.getRows());
}
JSON examples
The schema configuration for single-level and multi-level JSON fields follows the same structure for both Object and Nested. Only the type setting differs.
Single-level JSON
The following Java example creates a single-level JSON field tags (select the JSON type based on your requirements) with three sub-fields:
tagName: Keyword type. Used for exact matching and aggregation of tag names.
score: Double type. Used for numerical calculations and sorting of tag weights.
time: Date type in epoch_millis format. Used for time range queries and time series analysis.
Both array and non-array formats are supported for data writes:
// Array format
[{"tagName":"tag1", "score":0.8,"time": 1730690237000 }, {"tagName":"tag2", "score":0.2,"time": 1730691557000}]
// Non-array format
{"tagName":"tag1", "score":0.8,"time": 1730690237000 }
Complete schema configuration:
List<FieldSchema> subFieldSchemas = new ArrayList<FieldSchema>();
subFieldSchemas.add(new FieldSchema("tagName", FieldType.KEYWORD)
.setIndex(true).setEnableSortAndAgg(true));
subFieldSchemas.add(new FieldSchema("score", FieldType.DOUBLE)
.setIndex(true).setEnableSortAndAgg(true));
subFieldSchemas.add(new FieldSchema("time", FieldType.DATE)
.setDateFormats(Arrays.asList("epoch_millis")));
FieldSchema nestedFieldSchema = new FieldSchema("tags", FieldType.Json)
.setJsonType(JsonType.OBJECT) // Replace with JsonType.NESTED as needed
.setSubFieldSchemas(subFieldSchemas);
Multi-level JSON
The following Java example creates a multi-level JSON field user (select the JSON type based on your requirements) with basic user information and nested address data. Multi-level JSON fields support a mix of Nested and Object types.
Basic fields: name (Keyword, for exact name queries), age (Long, for age range filtering), birth (Date, format yyyy-MM-dd HH:mm:ss.SSS, for birthday queries), phone (Keyword, for contact matching).
Nested field:
address(select the JSON type based on your requirements), containingprovince,city, andstreet(all Keyword type) for hierarchical location queries.
Sample user data:
{
"name": "John",
"age": 20,
"birth": "2014-10-10 12:00:00.000",
"phone": "1390000****",
"address": {
"province": "Zhejiang",
"city": "Hangzhou",
"street": "1201, Sunshine Community, Yangguang Avenue"
}
}
Complete schema configuration for multi-level JSON:
// Sub-field schema for address (path: user.address)
List<FieldSchema> addressSubFiledSchemas = new ArrayList<>();
addressSubFiledSchemas.add(new FieldSchema("province",FieldType.KEYWORD));
addressSubFiledSchemas.add(new FieldSchema("city",FieldType.KEYWORD));
addressSubFiledSchemas.add(new FieldSchema("street",FieldType.KEYWORD));
// Sub-field schema for user (path: user)
List<FieldSchema> subFieldSchemas = new ArrayList<>();
subFieldSchemas.add(new FieldSchema("name",FieldType.KEYWORD));
subFieldSchemas.add(new FieldSchema("age",FieldType.LONG));
subFieldSchemas.add(new FieldSchema("birth",FieldType.DATE)
.setDateFormats(Arrays.asList("yyyy-MM-dd HH:mm:ss.SSS")));
subFieldSchemas.add(new FieldSchema("phone",FieldType.KEYWORD));
subFieldSchemas.add(new FieldSchema("address",FieldType.JSON)
.setJsonType(JsonType.NESTED) // Replace with JsonType.OBJECT as needed
.setSubFieldSchemas(addressSubFiledSchemas));
// Create the parent field: user
List<FieldSchema> fieldSchemas = new ArrayList<>();
fieldSchemas.add(new FieldSchema("user",FieldType.JSON)
.setJsonType(JsonType.OBJECT) // Replace with JsonType.NESTED as needed
.setSubFieldSchemas(subFieldSchemas));
Limitations
Vector fields: Vector fields cannot be used as sub-fields of a JSON field. Configure vector field indexes independently.
Nested type: Nested JSON fields do not support index pre-sorting. Use NestedQuery to query nested fields.
Array sub-fields: For non-JSON sub-fields with array-format data, set the IsArray property to true. Write data in the standard array format
`"[a, b, c]"`. Otherwise, the index cannot sync or query the sub-field.
References
The JSON field type is currently supported through Tablestore SDK for Java, Tablestore SDK for Go, and Tablestore SDK for Python.