JSON 类型
Zilliz Cloud 允许您使用 JSON
数据类型在单个字段中存储和索引结构化数据。这使得灵活的 schema 能够包含嵌套属性,同时仍然允许通过 JSON 路径索引进行高效过滤。
什么是 JSON 字段?
JSON 字段是 Zilliz Cloud 中定义在 schema 中的字段,用于存储结构化的键值对数据。值可以包括字符串、数值、布尔值、数组或深度嵌套的对象。
以下是 JSON 字段的示例:
{
"metadata": {
"category": "electronics",
"brand": "BrandA",
"in_stock": true,
"price": 99.99,
"string_price": "99.99",
"tags": ["clearance", "summer_sale"],
"supplier": {
"name": "SupplierX",
"country": "USA",
"contact": {
"email": "support@supplierx.com",
"phone": "+1-800-555-0199"
}
}
}
}
在这个示例中:
-
metadata
是可以在 schema 中定义的 JSON 字段。 -
您可以存储扁平值(如
category
、in_stock
)、数组(tags
)和嵌套对象(supplier
)等。
在 schema 中定义 JSON 字段
要使用 JSON 字段,需要在 collection schema 中明确定义它,将 DataType
指定为 JSON
。
以下示例创建一个包含以下字段的 collection 及其 schema:
-
主键(
product_id
) -
vector
字段(每个 collection 必须有至少一个向量字段) -
metadata
字段,类型为JSON
,可以存储结构化数据,如扁平值、数组或嵌套对象
- Python
- Java
- NodeJS
- Go
- cURL
from pymilvus import MilvusClient, DataType
client = MilvusClient(uri="YOUR_CLUSTER_ENDPOINT")
# 创建包含 JSON 字段的 schema
schema = client.create_schema(auto_id=False, enable_dynamic_field=True)
schema.add_field(field_name="product_id", datatype=DataType.INT64, is_primary=True)
schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=5)
# highlight-next-line
schema.add_field(field_name="metadata", datatype=DataType.JSON, nullable=True) # 允许空值的 JSON 字段
client.create_collection(
collection_name="product_catalog",
schema=schema
)
import io.milvus.v2.client.*;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
import io.milvus.v2.service.collection.request.AddFieldReq;
ConnectConfig config = ConnectConfig.builder()
.uri("YOUR_CLUSTER_ENDPOINT")
.build();
MilvusClientV2 client = new MilvusClientV2(config);
CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
.enableDynamicField(true)
.build();
schema.addField(AddFieldReq.builder()
.fieldName("product_id")
.dataType(DataType.Int64)
.isPrimaryKey(Boolean.TRUE)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("vector")
.dataType(DataType.FloatVector)
.dimension(5)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("metadata")
.dataType(DataType.JSON)
.isNullable(true)
.build());
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName("product_catalog")
.collectionSchema(schema)
.build();
client.createCollection(requestCreate);
import { MilvusClient, DataType } from '@zilliz/milvus2-sdk-node';
const client = new MilvusClient({
address: 'localhost:19530'
});
// Create collection
await client.createCollection({
collection_name: "product_catalog",
fields: [
{
name: "product_id",
data_type: DataType.Int64,
is_primary_key: true,
autoID: false
},
{
name: "vector",
data_type: DataType.FloatVector,
dim: 5
},
{
name: "metadata",
data_type: DataType.JSON,
nullable: true // JSON field that allows null values
}
],
enable_dynamic_field: true
});
import (
"context"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: "YOUR_CLUSTER_ENDPOINT",
})
if err != nil {
return err
}
schema := entity.NewSchema().WithDynamicFieldEnabled(true)
schema.WithField(entity.NewField().
WithName("product_id").pk
WithDataType(entity.FieldTypeInt64).
WithIsPrimaryKey(true),
).WithField(entity.NewField().
WithName("vector").
WithDataType(entity.FieldTypeFloatVector).
WithDim(5),
).WithField(entity.NewField().
WithName("metadata").
WithDataType(entity.FieldTypeJSON).
WithNullable(true),
)
err = client.CreateCollection(ctx, milvusclient.NewCreateCollectionOption("product_catalog", schema))
if err != nil {
return err
}
# restful
您还可以启用 dynamic field 功能来灵活存储未声明的字段,但这不是 JSON 字段正常工作的必要条件。更多信息请参考 Dynamic Field。
插入带有 JSON 数据的 entity
创建 collection 后,插入在 metadata
JSON 字段中包含结构化 JSON 对象的实体。
- Python
- Java
- NodeJS
- Go
- cURL
entities = [
{
"product_id": 1,
"vector": [0.1, 0.2, 0.3, 0.4, 0.5],
"metadata": {
"category": "electronics",
"brand": "BrandA",
"in_stock": True,
"price": 99.99,
"string_price": "99.99",
"tags": ["clearance", "summer_sale"],
"supplier": {
"name": "SupplierX",
"country": "USA",
"contact": {
"email": "support@supplierx.com",
"phone": "+1-800-555-0199"
}
}
}
}
]
client.insert(collection_name="product_catalog", data=entities)
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
Gson gson = new Gson();
JsonObject row = new JsonObject();
row.addProperty("product_id", 1);
row.add("vector", gson.toJsonTree(Arrays.asList(0.1, 0.2, 0.3, 0.4, 0.5)));
JsonObject metadata = new JsonObject();
metadata.addProperty("category", "electronics");
metadata.addProperty("brand", "BrandA");
metadata.addProperty("in_stock", true);
metadata.addProperty("price", 99.99);
metadata.addProperty("string_price", "99.99");
metadata.add("tags", gson.toJsonTree(Arrays.asList("clearance", "summer_sale")));
JsonObject supplier = new JsonObject();
supplier.addProperty("name", "SupplierX");
supplier.addProperty("country", "USA");
JsonObject contact = new JsonObject();
contact.addProperty("email", "support@supplierx.com");
contact.addProperty("phone", "+1-800-555-0199");
supplier.add("contact", contact);
metadata.add("supplier", supplier);
row.add("metadata", metadata);
client.insert(InsertReq.builder()
.collectionName("product_catalog")
.data(Collections.singletonList(row))
.build());
const entities = [
{
"product_id": 1,
"vector": [0.1, 0.2, 0.3, 0.4, 0.5],
"metadata": {
"category": "electronics",
"brand": "BrandA",
"in_stock": True,
"price": 99.99,
"string_price": "99.99",
"tags": ["clearance", "summer_sale"],
"supplier": {
"name": "SupplierX",
"country": "USA",
"contact": {
"email": "support@supplierx.com",
"phone": "+1-800-555-0199"
}
}
}
}
]
await client.insert({
collection_name: "product_catalog",
data: entities
});
_, err = client.Insert(ctx, milvusclient.NewColumnBasedInsertOption("product_catalog").
WithInt64Column("product_id", []int64{1}).
WithFloatVectorColumn("vector", 5, [][]float32{
{0.1, 0.2, 0.3, 0.4, 0.5},
}).WithColumns(
column.NewColumnJSONBytes("metadata", [][]byte{
[]byte(`{
"category": "electronics",
"brand": "BrandA",
"in_stock": True,
"price": 99.99,
"string_price": "99.99",
"tags": ["clearance", "summer_sale"],
"supplier": {
"name": "SupplierX",
"country": "USA",
"contact": {
"email": "support@supplierx.com",
"phone": "+1-800-555-0199"
}
}
}`),
}),
))
if err != nil {
return err
}
# restful
为 JSON 字段内的值建立索引
为了加速 JSON 字段的标量过滤,Zilliz Cloud 支持使用 JSON 路径索引为 JSON 字段建立索引。这允许您按 JSON 对象内的键或嵌套值进行过滤,而无需扫描整个字段。
为 JSON 字段建立索引是可选操作。您仍然可以在没有索引的情况下按 JSON 路径查询或过滤,但由于需要进行暴力搜索,性能可能会较慢。
JSON 路径索引语法
要创建 JSON 路径索引,请指定:
-
JSON 路径(
json_path
):您要索引的 JSON 对象内键或嵌套字段的路径。-
示例:
metadata["category"]
这定义了索引引擎在 JSON 结构内查找的位置。
-
-
JSON 转换类型(
json_cast_type
):Zilliz Cloud 在解释和索引指定路径处的值时应使用的数据类型。
支持的 JSON 转换类型
转换类型不区分大小写。支持以下类型:
转换类型 | 描述 | 示例 JSON 值 |
---|---|---|
| 布尔值 |
|
| 数值(整数或浮点数) |
|
| 字符串值 |
|
| 布尔值数组 |
|
| 数值数组 |
|
| 字符串数组 |
|
为了优化索引,数组应包含相同类型的元素。更多信息请参考 Array 类型。
示例:创建 JSON 路径索引
使用我们介绍中的 metadata
JSON 结构,以下是为不同 JSON 路径创建索引的示例:
- Python
- Java
- NodeJS
- Go
- cURL
# 将 category 字段作为字符串索引
index_params = client.prepare_index_params()
index_params.add_index(
field_name="metadata",
# highlight-next-line
index_type="AUTOINDEX", # 对于 JSON 路径索引必须设置为 AUTOINDEX
index_name="category_index", # 唯一的索引名称
# highlight-start
params={
"json_path": "metadata[\"category\"]", # 要索引的 JSON 键的路径
"json_cast_type": "varchar" # 数据转换类型
}
# highlight-end
)
# 将 tags 数组作为字符串数组索引
index_params.add_index(
field_name="metadata",
# highlight-next-line
index_type="AUTOINDEX", # 对于 JSON 路径索引必须设置为 AUTOINDEX
index_name="tags_array_index", # 唯一的索引名称
# highlight-start
params={
"json_path": "metadata[\"tags\"]", # 要索引的 JSON 键的路径
"json_cast_type": "array_varchar" # 数据转换类型
}
# highlight-end
)
import io.milvus.v2.common.IndexParam;
Map<String,Object> extraParams1 = new HashMap<>();
extraParams1.put("json_path", "metadata[\"category\"]");
extraParams1.put("json_cast_type", "varchar");
indexParams.add(IndexParam.builder()
.fieldName("metadata")
.indexName("category_index")
.indexType(IndexParam.IndexType.AUTOINDEX)
.extraParams(extraParams1)
.build());
Map<String,Object> extraParams2 = new HashMap<>();
extraParams2.put("json_path", "metadata[\"tags\"]");
extraParams2.put("json_cast_type", "array_varchar");
indexParams.add(IndexParam.builder()
.fieldName("metadata")
.indexName("tags_array_index")
.indexType(IndexParam.IndexType.AUTOINDEX)
.extraParams(extraParams2)
.build());
const indexParams = [
{
collection_name: "product_catalog",
field_name: "metadata",
index_name: "category_index",
index_type: "AUTOINDEX", // Can also use "INVERTED" for JSON path indexing
extra_params: {
json_path: 'metadata["category"]',
json_cast_type: "varchar",
},
},
{
collection_name: "product_catalog",
field_name: "metadata",
index_name: "tags_array_index",
index_type: "AUTOINDEX", // Can also use "INVERTED" for JSON path indexing
extra_params: {
json_path: 'metadata["tags"]',
json_cast_type: "array_varchar",
},
},
];
import (
"github.com/milvus-io/milvus/client/v2/index"
)
jsonIndex1 := index.NewJSONPathIndex(index.AUTOINDEX, "varchar", `metadata["category"]`)
.WithIndexName("category_index")
jsonIndex2 := index.NewJSONPathIndex(index.AUTOINDEX, "array_varchar", `metadata["tags"]`)
.WithIndexName("tags_array_index")
indexOpt1 := milvusclient.NewCreateIndexOption("product_catalog", "metadata", jsonIndex1)
indexOpt2 := milvusclient.NewCreateIndexOption("product_catalog", "metadata", jsonIndex2)
# restful
使用 JSON 转换函数进行类型转换
如果您的 JSON 字段键包含格式不正确的值(例如,以字符串形式存储的数字),您可以使用转换函数在索引期间转换值。
支持的转换函数
转换函数不区分大小写。支持以下类型:
转换函数 | 转换类型 | 使用场景 |
---|---|---|
| 字符串 → 数值 ( | 将 |
示例:将字符串数字转换为 double
- Python
- Java
- NodeJS
- Go
- cURL
# 将字符串数字转换为 double 用于索引
index_params.add_index(
field_name="metadata",
# highlight-next-line
index_type="AUTOINDEX", # 对于 JSON 路径索引必须设置为 AUTOINDEX
index_name="string_to_double_index", # 唯一的索引名称
params={
"json_path": "metadata[\"string_price\"]", # 要索引的 JSON 键的路径
"json_cast_type": "double", # 数据转换类型
# highlight-next-line
"json_cast_function": "STRING_TO_DOUBLE" # 转换函数;不区分大小写
}
)
Map<String,Object> extraParams3 = new HashMap<>();
extraParams3.put("json_path", "metadata[\"string_price\"]");
extraParams3.put("json_cast_type", "double");
extraParams3.put("json_cast_function", "STRING_TO_DOUBLE");
indexParams.add(IndexParam.builder()
.fieldName("metadata")
.indexName("string_to_double_index")
.indexType(IndexParam.IndexType.AUTOINDEX)
.extraParams(extraParams3)
.build());
indexParams.push({
collection_name: "product_catalog",
field_name: "metadata",
index_name: "string_to_double_index",
index_type: "AUTOINDEX", // Can also use "INVERTED"
extra_params: {
json_path: 'metadata["string_price"]',
json_cast_type: "double",
json_cast_function: "STRING_TO_DOUBLE", // Case insensitive
},
});
jsonIndex3 := index.NewJSONPathIndex(index.AUTOINDEX, "double", `metadata["string_price"]`)
.WithIndexName("string_to_double_index")
indexOpt3 := milvusclient.NewCreateIndexOption("product_catalog", "metadata", jsonIndex3)
# restful
json_cast_type
参数是必需的,且必须与转换函数的输出类型相同。如果转换失败(例如,非数字字符串),该值将被跳过并且不会被索引。
将索引参数应用到 collection
定义索引参数后,您可以使用 create_index()
将它们应用到 collection:
- Python
- Java
- NodeJS
- Go
- cURL
client.create_index(
collection_name="product_catalog",
index_params=index_params
)
import io.milvus.v2.service.index.request.CreateIndexReq;
client.createIndex(CreateIndexReq.builder()
.collectionName("product_catalog")
.indexParams(indexParams)
.build());
await client.createIndex(indexParams)
indexTask1, err := client.CreateIndex(ctx, indexOpt1)
if err != nil {
return err
}
indexTask2, err := client.CreateIndex(ctx, indexOpt2)
if err != nil {
return err
}
indexTask3, err := client.CreateIndex(ctx, indexOpt3)
if err != nil {
return err
}
# restful
按 JSON 字段值过滤
插入和索引 JSON 字段后,您可以使用标准过滤表达式和 JSON 路径语法对它们进行过滤。
例如:
- Python
- Java
- NodeJS
- Go
- cURL
filter = 'metadata["category"] == "electronics"'
filter = 'metadata["price"] > 50'
filter = 'json_contains(metadata["tags"], "featured")'
String filter = 'metadata["category"] == "electronics"';
String filter = 'metadata["price"] > 50';
String filter = 'json_contains(metadata["tags"], "featured")';
let filter = 'metadata["category"] == "electronics"'
let filter = 'metadata["price"] > 50'
let filter = 'json_contains(metadata["tags"], "featured")'
filter := 'metadata["category"] == "electronics"'
filter := 'metadata["price"] > 50'
filter := 'json_contains(metadata["tags"], "featured")'
# restful
要在搜索或查询中使用这些表达式,请确保:
-
您已为每个向量字段创建了索引。
-
collection 已加载到内存中。
有关支持的操作符和表达式的完整列表,请参考 JSON 操作符。
整体流程
到目前为止,您已经学会了如何定义、插入和可选地为 JSON 字段内的结构化值建立索引。
要在实际应用中完成工作流程,您还需要:
-
为您的向量字段创建索引(每个 collection 中的每个向量字段都必须)
-
加载 collection
-
使用 JSON 过滤表达式进行搜索或查询
参考 Filtered Search 和 JSON 操作符
常见问题
JSON 字段和 dynamic field 有什么区别?
-
JSON 字段是 schema 定义的。您必须在 schema 中明确声明该字段。
-
Dynamic field是一个隐藏的 JSON 对象(
$meta
),它自动存储任何未在 schema 中定义的字段。
两者都支持嵌套结构和 JSON 路径索引,但 dynamic field 更适合可选或不断变化的数据结构。
详情请参考 Dynamic Field。
JSON 字段的大小有限制吗?
有的。每个 JSON 字段限制为 65,536 字节。
JSON 字段支持设置默认值吗?
不支持,JSON 字段不支持默认值。但是,您可以在定义字段时设置 nullable=True
来允许空条目。
详情请参考 Nullable 和默认值。
JSON 字段键有命名规范吗?
有的。为了确保与查询和索引的兼容性:
-
在 JSON 键中只使用字母、数字和下划线。
-
避免使用特殊字符、空格或点(
.
、/
等)。 -
不兼容的键可能会在过滤表达式中导致解析问题。
Zilliz Cloud 如何处理 JSON 字段中的字符串值?
Zilliz Cloud 完全按照 JSON 输入中的字符串值存储——不进行语义转换。引号不当的字符串可能会在解析过程中导致错误。
有效字符串示例:
"a\"b", "a'b", "a\b"
无效字符串示例:
'a"b', 'a\'b'
Zilliz Cloud 对索引的 JSON 路径使用什么过滤逻辑?
-
数值索引:
如果使用
json_cast_type="double"
创建索引,只有数值过滤条件(例如>
、<
、== 42
)会利用索引。非数值条件将强制进行暴力扫描。 -
字符串索引:
如果索引使用
json_cast_type="varchar"
,只有字符串过滤条件会受益于索引;其他类型将回退到暴力搜索。 -
布尔索引:
布尔索引的行为类似于字符串索引,只有条件严格匹配 true 或 false 时才会使用索引。
Term 表达式如何与 JSON 字段索引配合使用?
您可以使用 term 表达式如 json["field"] IN [value1, value2, …]
来过滤 entity。
-
只有当目标值是标量时才会应用索引。
-
如果
json["field"]
是数组,查询将不会使用索引,将回退到暴力搜索。
索引 JSON 字段时的数值精度如何?
Zilliz Cloud 将所有索引的数值存储为 double。
如果数值超过 2^53
,可能会失去精度。这种精度损失可能导致过滤查询无法精确匹配超出范围的值。
Zilliz Cloud 如何处理 JSON 字段索引的数据完整性?
Zilliz Cloud 不会自动转换或规范化不一致的数据类型。
例如,如果某些行将 "price": "99.99"
存储为字符串,而其他行将 "price": 99.99
存储为数字,同时索引定义为 double,只有具有数值的行才会被索引。
不一致会导致受影响的行在索引期间被静默跳过。
索引 JSON 字段时类型转换失败会怎样?
如果值无法转换为指定的 json_cast_type
(例如,期望 double
时遇到非数字字符串),该值会被静默跳过并且不包含在索引中。因此,具有转换失败的实体将从依赖索引的过滤结果中排除。
为了避免意外的查询行为,请确保索引的 JSON 路径下的所有值都具有一致的类型。
我可以在同一个 JSON 路径上使用不同的转换类型创建多个索引吗?
不可以,每个 JSON 路径只支持一个索引。您必须选择一个与您的数据匹配的 json_cast_type
。不支持在同一路径上使用不同转换类型创建多个索引。
如果 JSON 路径上的值具有不一致的类型怎么办?
跨实体的不一致类型可能导致部分索引。例如,如果 metadata["price"]
既存储为数字(99.99
)又存储为字符串("99.99"
),而索引定义为 json_cast_type="double"
,只有数值会被索引。字符串形式的条目将被跳过,不会出现在过滤结果中。
我可以使用与索引转换类型不同的类型进行过滤吗?
如果您的过滤表达式使用的类型与索引的 json_cast_type
不同,系统将不会使用索引,并且可能会回退到更慢的暴力扫描——如果数据允许的话。为了获得最佳性能,请始终将过滤表达式与索引的转换类型保持一致。