Lindorm SQL provides JSON functions for constructing, extracting, checking containment in, and updating JSON data stored in JSON columns. This reference describes each function's syntax, parameters, behavior, and examples.
Function overview
| Category | Function | Description |
|---|---|---|
| Constructor | json_object | Creates a JSON object. |
| Constructor | json_array | Creates a JSON array. |
| Extraction | json_extract | Extracts a value at a specified path. |
| Extraction | json_extract_string | Extracts a value at a specified path and returns it as VARCHAR. |
| Extraction | json_extract_long | Extracts a value at a specified path and returns it as LONG. |
| Extraction | json_extract_double | Extracts a value at a specified path and returns it as Double. |
| Containment check | json_contains | Returns 1 if the JSON document contains all specified values. |
| Containment check | json_contains_any | Returns 1 if the JSON document contains any of the specified values. |
| Update | json_set | Inserts or updates a value at a path. Equivalent to json_insert + json_replace. |
| Update | json_insert | Inserts a value at a path only if the path does not exist. |
| Update | json_replace | Updates a value at a path only if the path already exists. |
| Update | json_remove | Deletes the value at a specified path. |
| Update | json_upsert | Same as json_set, but creates a new object if the JSON column is NULL. |
JSON path expressions
All extraction, containment check, and update functions accept a JSON path expression to identify a location within a JSON document.
| Symbol | Description | Example |
|---|---|---|
$ | Document root | $ |
.key | Object member access | $.name |
[n] | Array element access (0-indexed) | $.skills[0] |
.nested.key | Nested field access | $.address.city |
Example JSON document:
{
"name": "Alice",
"age": 25,
"address": { "city": "Beijing" },
"skills": ["Java", "Python"]
}| Path | Result |
|---|---|
$.name | "Alice" |
$.age | 25 |
$.address.city | "Beijing" |
$.skills[0] | "Java" |
$.skills | ["Java", "Python"] |
Constructor functions
json_object
Creates a JSON object from key-value pairs.
Syntax
json_object(key1, value1, key2, value2, ...)Parameters
| Parameter | Description |
|---|---|
key | The key name. Must be a string. |
value | The value. Can be of any type. |
Examples
Create a simple object:
SELECT json_object('name', 'Alice', 'age', 25);
-- Result: {"name": "Alice", "age": 25}Use in an UPSERT statement:
UPSERT INTO users (id, data) VALUES (1, json_object('name', 'Charlie', 'city', 'Beijing'));json_array
Creates a JSON array from a list of values.
Syntax
json_array(value1, value2, value3, ...)Parameters
| Parameter | Description |
|---|---|
value | The value. Can be a scalar, object, or array. |
Examples
Create a simple array:
SELECT json_array('apple', 'banana', 'orange');
-- Result: ["apple", "banana", "orange"]Use in an UPSERT statement:
UPSERT INTO products (id, tags) VALUES (1, json_array('electronics', 'mobile', 'smartphone'));Extraction functions
All extraction functions accept a JSON column and a path expression. The three typed extraction functions (json_extract_string, json_extract_long, json_extract_double) are strongly typed — the value at the path must match the function's expected type. If the types do not match, the function returns NULL or throws an error. Define data types before inserting JSON data to avoid unexpected behavior from mixed types.
json_extract
Extracts a value from a JSON document at a specified path and returns it in its native JSON type.
Syntax
json_extract(json_column, 'path')Parameters
| Parameter | Description |
|---|---|
json_column | A column or expression of the JSON type. |
path | A JSON path expression, such as $.field, $.array[0], or $.nested.field. |
Examples
Extract a top-level field:
SELECT json_extract('{"name": "Alice", "age": 25}', '$.name');
-- Result: "Alice"Extract a nested field:
SELECT json_extract('{"user": {"name": "Bob", "age": 30}}', '$.user.name');
-- Result: "Bob"Extract an array element:
SELECT json_extract('{"skills": ["Java", "Python"]}', '$.skills[0]');
-- Result: "Java"Extract an entire array:
SELECT json_extract('{"skills": ["Java", "Python"]}', '$.skills');
-- Result: ["Java", "Python"]Use in a WHERE clause with a numeric comparison:
SELECT * FROM tb WHERE json_extract(c2, '$.k3.k4') > 5;Use in a WHERE clause with a string comparison (use json_extract_string for type safety):
SELECT * FROM tb WHERE json_extract_string(c2, '$.k2') = '1';json_extract_string
Extracts a value at a specified path and returns it as VARCHAR. If the value at the path is not a string, the function returns NULL.
Examples
The value at $.name is a string, so it returns a VARCHAR:
SELECT json_extract_string('{"name": "Alice"}', '$.name');
-- Result: Alice (VARCHAR)The value at $.number is a number, not a string, so the function returns NULL:
SELECT json_extract_string('{"number": 30}', '$.number');
-- Result: NULLjson_extract_long
Extracts a value at a specified path and returns it as LONG. If the value at the path is not a number, Java Database Connectivity (JDBC) throws an error.
Examples
The value is an integer, so it returns a LONG:
SELECT json_extract_long('{"id": 123456789}', '$.id');
-- Result: 123456789 (LONG)The value is a floating-point number — JDBC throws an error:
SELECT json_extract_long('{"id": 123456.789}', '$.id');
-- Throws an errorThe value is a numeric string — JDBC throws an error:
SELECT json_extract_long('{"id": "123456.789"}', '$.id');
-- Throws an errorjson_extract_double
Extracts a value at a specified path and returns it as Double. If the value at the path is not a floating-point number, JDBC throws an error.
Examples
The value is a floating-point number, so it returns a Double:
SELECT json_extract_double('{"id": 12345.56}', '$.id');
-- Result: 12345.56 (Double)The value is an integer — the function returns it as a Double:
SELECT json_extract_double('{"id": 12345}', '$.id');
-- Result: 12345.0 (Double)The value is a numeric string — JDBC throws an error:
SELECT json_extract_double('{"id": "123456.789"}', '$.id');
-- Throws an errorContainment check functions
json_contains
Returns 1 if target_json contains all elements in candidate_json, and 0 otherwise. Optionally, specify a path to check containment at a specific location within the document.
Returns NULL if the specified path does not exist or if any parameter is NULL.
Syntax
json_contains(target_json, candidate_json[, path])Parameters
| Parameter | Required | Description |
|---|---|---|
target_json | Yes | The JSON document to search in. |
candidate_json | Yes | The JSON document to check for. Must be a valid JSON string. |
path | No | The path to check within target_json. Defaults to $ (the root). |
How to format `candidate_json`
candidate_json must be a valid JSON string representing the element to check for:
| What to check for | candidate_json value |
|---|---|
The number 10 | '10' |
The string "10" | '"10"' |
| A list of numbers | '[1,2,3]' |
| A list of strings | '["10","abc","key"]' |
JSON type classifications
Scalar: String, Number (Integer/Double), Boolean, Null
Complex type: Array, Object
Containment rules
A scalar can only contain another scalar, not an array.
An array can contain an element (scalar) or a subset array.
Examples
Set up the example table used in the following queries:
CREATE TABLE test_table (id INT, c1 VARCHAR, data JSON, PRIMARY KEY(id));
UPSERT INTO test_table (id, data) VALUES (1, NULL);
UPSERT INTO test_table (id, data) VALUES (2, '{"skills": ["Java", "Python"]}');
UPSERT INTO test_table (id, data) VALUES (3, '{"skills": ["Go", "C"]}');
UPSERT INTO test_table (id, data) VALUES (4, '{"technical_skills": ["Java", "Go", "Rust"]}');
UPSERT INTO test_table (id, data) VALUES (5, '["Java","C++", "JavaScript"]');
UPSERT INTO test_table (id, data) VALUES (6, '{"skills": "Java"}');
UPSERT INTO test_table (id, data) VALUES (7, '["Java", "C#"]');
UPSERT INTO test_table (id, data) VALUES (8, '{"skills": ["Go", "Rust"]}');The two-parameter form checks the entire document (root path $). This finds rows where the top-level document is an array containing "Java":
SELECT id, data FROM test_table WHERE json_contains(data, '["Java"]') AND id > 0 AND id < 10;+------+------------------------------+
| id | data |
+------+------------------------------+
| 5 | ["Java","C++", "JavaScript"] |
| 7 | ["Java", "C#"] |
+------+------------------------------+The three-parameter form checks a specific path. This finds rows where $.skills is an array containing "Java":
SELECT id, data FROM test_table WHERE json_contains(data, '["Java"]', '$.skills') AND id > 0 AND id < 10;+------+--------------------------------+
| id | data |
+------+--------------------------------+
| 2 | {"skills": ["Java", "Python"]} |
+------+--------------------------------+json_contains requires all elements of candidate_json to be present. This query finds no rows because no $.skills array contains both "Java" and "Go":
SELECT id, data FROM test_table WHERE json_contains(data, '["Java","Go"]', '$.skills') AND id > 0 AND id < 10;
-- Empty setUsing a scalar "Java" as the candidate matches both an array containing "Java" and a scalar equal to "Java":
SELECT id, data FROM test_table WHERE json_contains(data, '"Java"', '$.skills') AND id > 0 AND id < 10;+------+--------------------------------+
| id | data |
+------+--------------------------------+
| 2 | {"skills": ["Java", "Python"]} |
| 6 | {"skills": "Java"} |
+------+--------------------------------+Check if an object contains a key-value pair:
SELECT json_contains('{"a": 1, "b": 2}', '{"a": 1}');
-- Result: 1Check if a specific path contains a given value:
SELECT json_contains('{"a": 1, "b": 2}', '1', '$.a');
-- Result: 1
SELECT json_contains('{"a": 1, "b": 2}', '1', '$.b');
-- Result: 0json_contains_any
Returns 1 if target_json contains at least one element from candidate_json. Uses the same syntax and parameters as json_contains.
Examples
Without a path, checks the root document. The top-level document is an object, not an array containing "Java" or "Go" directly:
SELECT json_contains_any('{"skills": ["Java", "Python"]}', '["Java", "Go"]');
-- Result: 0With a path, checks $.skills. The array at $.skills contains "Java", which matches one element of the candidate:
SELECT json_contains_any('{"skills": ["Java", "Python"]}', '["Java", "Go"]', '$.skills');
-- Result: 1The two-parameter form finds rows where the top-level document is an array with any element from ["Java", "Go"]:
SELECT id, data FROM test_table WHERE json_contains_any(data, '["Java", "Go"]') LIMIT 10;+------+------------------------------+
| id | data |
+------+------------------------------+
| 5 | ["Java","C++", "JavaScript"] |
| 7 | ["Java", "C#"] |
+------+------------------------------+The three-parameter form finds rows where $.skills contains any element from the candidate list:
SELECT id, data FROM test_table WHERE json_contains_any(data, '["Java", "Go", "Rust"]', '$.skills') LIMIT 10;+------+--------------------------------+
| id | data |
+------+--------------------------------+
| 2 | {"skills": ["Java", "Python"]} |
| 3 | {"skills": ["Go", "C"]} |
| 6 | {"skills": "Java"} |
| 8 | {"skills": ["Go", "Rust"]} |
+------+--------------------------------+Check if a specific path contains any value from the candidate array:
SELECT json_contains_any('{"departments": ["Engineering", "Sales"]}', '["Marketing", "Engineering"]', '$.departments');
-- Result: 1Understanding scalar vs. array containment at a path
Consider json_contains_any(data, '["Java"]', '$.technical_skills'). The behavior depends on the JSON type at $.technical_skills:
If the value is
{"technical_skills": "Java"}(a scalar string), the function returnsfalse. A subset of a scalar cannot be a list.If the value is
{"technical_skills": ["Java"]}or{"technical_skills": ["Java", "Go"]}(an array), the function returnstrue.
Consider json_contains_any(data, '"Java"', '$.technical_skills'). Using a scalar candidate:
If the value is
{"technical_skills": "Java"}(a scalar string equal to"Java"), the function returnstrue.If the value is
{"technical_skills": ["Java"]}or{"technical_skills": ["Java", "Go"]}(an array containing"Java"), the function returnstrue.
Update functions
json_set
Inserts or updates a value at a specified path. If the path exists, updates the value. If the path does not exist, inserts the value. If the JSON column is NULL, the result is NULL — no new object is created.
Equivalent to json_insert + json_replace.
Syntax
json_set(json_column, 'path', new_value)Examples
Update an existing field:
UPSERT INTO test_table (id, data) VALUES (3, '{"name": "Charlie", "age": 30}');
UPDATE test_table SET data = json_set(data, '$.age', 31) WHERE id = 3;
-- Result: {"name": "Charlie", "age": 31}Insert a new field:
UPDATE test_table SET data = json_set(data, '$.department', 'Engineering') WHERE id = 3;
-- Result: {"name": "Charlie", "age": 31, "department": "Engineering"}If the JSON column is NULL, the result remains NULL:
UPSERT INTO test_table (id, c1) VALUES (4, 'test');
UPDATE test_table SET data = json_set(data, '$.name', 'David') WHERE id = 4;
-- Result: NULLjson_insert
Inserts a value at a specified path only if the path does not already exist. If the path already exists, the function takes no action.
json_insert does not overwrite existing values. An attempt to insert into an existing field silently does nothing — the original value is preserved.Syntax
json_insert(json_column, 'path', new_value)Examples
Insert a new field:
UPSERT INTO test_table (id, data) VALUES (5, '{"name": "Eve"}');
UPDATE test_table SET data = json_insert(data, '$.age', 28) WHERE id = 5;
-- Result: {"name": "Eve", "age": 28}Attempting to insert into an existing field has no effect — the name field remains unchanged:
UPDATE test_table SET data = json_insert(data, '$.name', 'New Name') WHERE id = 5;
-- Result: {"name": "Eve", "age": 28}json_replace
Updates a value at a specified path only if the path already exists. If the path does not exist, the function takes no action.
Syntax
json_replace(json_column, 'path', new_value)Examples
Update an existing field:
UPSERT INTO test_table (id, data) VALUES (6, '{"name": "Frank", "age": 35}');
UPDATE test_table SET data = json_replace(data, '$.age', 36) WHERE id = 6;
-- Result: {"name": "Frank", "age": 36}Attempting to update a non-existent field has no effect — no city field is added:
UPDATE test_table SET data = json_replace(data, '$.city', 'Shanghai') WHERE id = 6;
-- Result: {"name": "Frank", "age": 36}json_remove
Deletes the value at a specified path and returns the modified document. If the path does not exist, the operation succeeds without error.
Syntax
json_remove(json_column, 'path')Examples
Delete a field:
UPSERT INTO test_table (id, data) VALUES (7, '{"name": "Grace", "temp_field": "to_remove"}');
UPDATE test_table SET data = json_remove(data, '$.temp_field') WHERE id = 7;
-- Result: {"name": "Grace"}Deleting a non-existent field does nothing and returns the document unchanged:
UPDATE test_table SET data = json_remove(data, '$.nonexistent') WHERE id = 7;
-- Result: {"name": "Grace"}json_upsert
Inserts or updates a value at a specified path. Behaves like json_set, with one key difference: if the JSON column is NULL, json_upsert creates a new JSON object instead of returning NULL.
Syntax
json_upsert(json_column, 'path', new_value)Examples
When the JSON column is NULL, json_upsert creates a new object:
CREATE TABLE test_table (id INT, c1 VARCHAR, data JSON, PRIMARY KEY(id));
UPSERT INTO test_table (id, c1) VALUES (1, 'test');
UPDATE test_table SET data = json_upsert(data, '$.name', 'Alice') WHERE id = 1;
-- Result: {"name": "Alice"}Update an existing field:
UPSERT INTO test_table (id, data) VALUES (2, '{"name": "Bob", "age": 25}');
UPDATE test_table SET data = json_upsert(data, '$.age', 26) WHERE id = 2;
-- Result: {"name": "Bob", "age": 26}Insert a new field:
UPDATE test_table SET data = json_upsert(data, '$.city', 'Beijing') WHERE id = 2;
-- Result: {"name": "Bob", "age": 26, "city": "Beijing"}Function comparison
Containment check functions
| Function | Behavior |
|---|---|
json_contains | Every element in candidate_json must be in target_json. (candidate_json is a subset of target_json.) |
json_contains_any | At least one element in candidate_json must be in target_json. |
Update functions
| Function | NULL column | Path exists | Path absent | Use when |
|---|---|---|---|---|
json_set | Remains NULL | Updates | Inserts | The column is known to be not NULL. Aligns with MySQL semantics. |
json_upsert | Creates {path: value} | Updates | Inserts | General-purpose updates, including when the column may be NULL. |
json_insert | Remains NULL | No action | Inserts | Adding new fields without modifying existing values. |
json_replace | Remains NULL | Updates | No action | Updating existing fields without inserting new ones. |
json_remove | Remains NULL | Deletes | No action | Removing fields. |
Function-based indexes
Create function-based indexes to accelerate queries on specific JSON paths in a wide table.
Syntax
CREATE INDEX [index_name]
ON table_name (json_extract_type(column, json_path))
[INCLUDE (column_name1, ..., column_namen)]
[ASYNC]
[index_options]Parameters
| Parameter | Description |
|---|---|
index_name | The name of the index table. |
table_name | The name of the wide table. |
json_extract_type | The extraction function that defines the indexed path. Supported functions: json_extract_string, json_extract_long, json_extract_double. If the extracted value's data type does not match the function's return type, no secondary index is built for that row. |
column | The JSON column name. |
json_path | The path to index within the JSON column. |
ASYNC | Builds the index asynchronously. Without ASYNC, the index is built synchronously. |
Supported functions
The following functions support function-based index creation:
json_extract_string(json_column, 'path')— indexes string valuesjson_extract_long(json_column, 'path')— indexes long integer valuesjson_extract_double(json_column, 'path')— indexes floating-point valuesjson_contains(json_column, 'value', 'path')— indexes containment checks; thevalueandpathparameters must be fixed (static) values
Examples
Create an index to accelerate json_contains queries on $.roles:
CREATE INDEX idx_user_role ON test_table (json_contains(data, '"admin"', '$.roles')) INCLUDE(data) SYNC;Create an index on a string field at $.address.city:
CREATE INDEX idx_user_city ON test_table (json_extract_string(data, '$.address.city')) INCLUDE(data) SYNC;Create an index on a string field without including additional columns:
CREATE INDEX idx_user_city ON test_table (json_extract_string(data, '$.address.city'));Create an index on a numeric field at $.age:
CREATE INDEX idx_user_age ON test_table (json_extract_long(data, '$.age')) INCLUDE(data) SYNC;Verify the index was created:
SHOW INDEX FROM test_table;For JSON search index support, see Use search indexes for the Lindorm JSON type.
Limitations
Always use valid JSON path syntax. An
illegal json patherror occurs if the path expression is invalid. For path syntax details, see MySQL's JsonPath definition.Complex JSON operations can have significant performance overhead. Create function-based indexes for frequently queried paths.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
illegal json path | The path expression is not valid JSON path syntax. | Check the path syntax against the MySQL JsonPath definition. |
json_contain candidate is not a valid value | The candidate parameter is not a valid JSON string. | Verify the candidate value is correctly formatted as a JSON string. |
This query may be a full table scan and thus may have unpredictable performance | The query cannot use an index and scans the entire table. | Create a function-based index for the queried path, or add a LIMIT clause to reduce the result set. |
Best practices
Choose the right update function. Use json_upsert when the JSON column may be NULL or when you need general-purpose insert-or-update behavior. Use json_set when the column is known to be not NULL and you want MySQL-compatible semantics.
Index frequently queried paths. Create function-based indexes on JSON paths that appear in WHERE clauses or json_contains conditions. This avoids full table scans and significantly improves query performance.
Use typed extraction functions for type safety. Use json_extract_string, json_extract_long, and json_extract_double instead of json_extract when you need a specific return type. Define data types consistently before inserting JSON data — mixing numbers and numeric strings (for example, the number 10 and the string "10") causes ambiguous behavior and unexpected query results.