📦

MariaDB JSON Functions

MariaDB

Complete reference for MariaDB JSON functions covering JSON_EXTRACT, JSON_SET, JSON_CONTAINS, JSON_TABLE, JSON_MERGE_PATCH, JSON_ARRAYAGG, and 34+ more. Every function includes syntax, path expression examples, and differences from MySQL JSON functions. Updated for MariaDB 11.x.

31 functions

What are MariaDB JSON Functions?

MariaDB JSON functions provide full support for storing, querying, and modifying JSON data stored in TEXT or JSON column types. JSON_EXTRACT() retrieves values using path expressions (e.g. '$.key' or '$[0]'), JSON_SET() modifies values, JSON_CONTAINS() tests for value membership, and JSON_TABLE() (added in MariaDB 10.6) shreds a JSON array into a relational table. MariaDB JSON functions are broadly compatible with MySQL JSON functions but differ from PostgreSQL's JSONB operators.

JSON_EXTRACT

MariaDB 10.2+JSON

Extracts a value from a JSON document at the specified JSONPath location(s).

Signature

JSON_EXTRACT(json_doc, path[, path]...)

Parameters

ParameterTypeDescription
json_docJSONThe JSON document (stored as LONGTEXT in MariaDB)
pathVARCHARJSONPath expression such as $.key or $[0]

Examples

Extract scalar value

sql
SELECT JSON_EXTRACT('{"a":1}', '$.a');
-- 1

Array index (zero-based)

sql
SELECT JSON_EXTRACT('[1,2,3]', '$[1]');
-- 2

->> operator returns unquoted text

sql
SELECT col->>'$.name' FROM users;
-- Alice

Nested path

sql
SELECT JSON_EXTRACT(data, '$.addr.city') FROM customers;
-- "London"

Wildcard path returns array

sql
SELECT JSON_EXTRACT(data, '$.tags[*]') FROM posts;
-- ["sql","db"]
Anti-PatternForgetting the $ root token

Omitting the leading $ in the path (e.g. passing 'name' instead of '$.name') returns NULL silently.

✓ Instead: Always start paths with $ and test with JSON_VALID() before querying production data.

MariaDB Note

MariaDB stores JSON columns as LONGTEXT with a CHECK constraint; the value is validated on insert.

⇄ vs MySQL: Identical in MySQL 5.7+. The -> and ->> operators also work in both.
⇄ vs PostgreSQL: PostgreSQL uses the -> and ->> operators or jsonb_extract_path(); there is no JSON_EXTRACT() function.

JSON_VALUE

MariaDB 10.6.1+VARCHAR / typed via RETURNING clause

Extracts a scalar value from a JSON document at the given path and returns it as a SQL type.

Signature

JSON_VALUE(json_doc, path [RETURNING type] [on_empty] [on_error])

Parameters

ParameterTypeDescription
json_docJSONThe JSON document
pathVARCHARJSONPath expression pointing to a scalar
RETURNING typekeywordOptional SQL type to cast the result to (e.g. INT, DECIMAL, DATE)
on_emptykeywordNULL ON EMPTY, ERROR ON EMPTY, or DEFAULT val ON EMPTY
on_errorkeywordNULL ON ERROR, ERROR ON ERROR, or DEFAULT val ON ERROR

Examples

Typed extraction

sql
SELECT JSON_VALUE('{"price":9.99}', '$.price' RETURNING DECIMAL(10,2));
-- 9.99

Extract integer from nested key

sql
SELECT JSON_VALUE(data, '$.user.id' RETURNING INT) FROM orders;
-- 42

Default when path is absent

sql
SELECT JSON_VALUE('{}', '$.missing' DEFAULT 0 ON EMPTY);
-- 0

Return as DATE type

sql
SELECT JSON_VALUE(data, '$.created_at' RETURNING DATE) FROM events;
-- 2024-01-15

Suppress conversion errors

sql
SELECT JSON_VALUE(data, '$.score' RETURNING INT NULL ON ERROR) FROM results;
-- NULL
Anti-PatternUsing JSON_VALUE on non-scalar paths

Pointing JSON_VALUE at an object or array path returns NULL (or triggers ON ERROR) because it only returns scalars.

✓ Instead: Use JSON_EXTRACT for objects/arrays and JSON_VALUE only for leaf scalar values.

⇄ vs MySQL: Available in MySQL 8.0.21+ with the same syntax.
⇄ vs PostgreSQL: PostgreSQL uses ->> or json_extract_path_text() for untyped text; explicit CAST is separate.

JSON_SET

MariaDB 10.2+JSON

Inserts or updates values in a JSON document, replacing existing values and adding missing ones.

Signature

JSON_SET(json_doc, path, val[, path, val]...)

Parameters

ParameterTypeDescription
json_docJSONThe source JSON document
pathVARCHARJSONPath expression pointing to the target location
valANYThe new value to set at the given path

Examples

Update existing key

sql
SELECT JSON_SET('{"a":1}', '$.a', 2);
-- {"a": 2}

Insert new key

sql
SELECT JSON_SET('{"a":1}', '$.b', 2);
-- {"a": 1, "b": 2}

Multiple paths in one call

sql
SELECT JSON_SET(data, '$.status', 'active', '$.updated', NOW()) FROM orders WHERE id=1;
-- updated JSON

In-place column update

sql
UPDATE users SET profile = JSON_SET(profile, '$.score', score + 1) WHERE id = 42;
-- 1 row affected

Update array element

sql
SELECT JSON_SET('[1,2,3]', '$[1]', 99);
-- [1, 99, 3]
Anti-PatternChaining JSON_SET calls in application code

Calling JSON_SET separately for each key in application code generates multiple round-trips and intermediate strings.

✓ Instead: Pass all path-value pairs to a single JSON_SET call: JSON_SET(doc, '$.a', 1, '$.b', 2).

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_set() for jsonb or the || merge operator; no JSON_SET() function.

JSON_INSERT

MariaDB 10.2+JSON

Inserts new values into a JSON document only if the path does not already exist.

Signature

JSON_INSERT(json_doc, path, val[, path, val]...)

Parameters

ParameterTypeDescription
json_docJSONThe source JSON document
pathVARCHARJSONPath expression for the new value location
valANYThe value to insert

Examples

New key inserted

sql
SELECT JSON_INSERT('{"a":1}', '$.b', 2);
-- {"a": 1, "b": 2}

Existing key is NOT overwritten

sql
SELECT JSON_INSERT('{"a":1}', '$.a', 99);
-- {"a": 1}

Insert multiple keys

sql
SELECT JSON_INSERT('{}', '$.name', 'Alice', '$.age', 30);
-- {"name": "Alice", "age": 30}

Append to array

sql
SELECT JSON_INSERT('[1,2]', '$[2]', 3);
-- [1, 2, 3]

Conditional insert in update

sql
UPDATE orders SET meta = JSON_INSERT(meta, '$.created_at', NOW()) WHERE meta->>'$.created_at' IS NULL;
-- rows affected
Anti-PatternUsing JSON_INSERT expecting it to update

Developers coming from key-value stores expect JSON_INSERT to overwrite existing keys like a set operation.

✓ Instead: Use JSON_SET when you want upsert (insert-or-replace) semantics.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL has no direct equivalent; use jsonb_set() with create_missing=true or the || operator.

JSON_REPLACE

MariaDB 10.2+JSON

Replaces existing values in a JSON document, ignoring paths that do not exist.

Signature

JSON_REPLACE(json_doc, path, val[, path, val]...)

Parameters

ParameterTypeDescription
json_docJSONThe source JSON document
pathVARCHARJSONPath expression of the value to replace
valANYThe replacement value

Examples

Existing key replaced

sql
SELECT JSON_REPLACE('{"a":1}', '$.a', 99);
-- {"a": 99}

Non-existent key silently ignored

sql
SELECT JSON_REPLACE('{"a":1}', '$.b', 99);
-- {"a": 1}

Safe in-place update

sql
UPDATE users SET prefs = JSON_REPLACE(prefs, '$.theme', 'dark') WHERE id = 5;
-- 1 row affected

Replace array element

sql
SELECT JSON_REPLACE('[1,2,3]', '$[0]', 10);
-- [10, 2, 3]

Multiple replacements

sql
SELECT JSON_REPLACE(data, '$.status', 'closed', '$.closed_at', NOW()) FROM tickets;
-- updated JSON
Anti-PatternExpecting JSON_REPLACE to add missing keys

JSON_REPLACE silently skips paths that do not exist, so missing keys produce no error and no change.

✓ Instead: Use JSON_SET if you want insert-or-update behavior.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_set() with create_missing=false for update-only behavior.

JSON_REMOVE

MariaDB 10.2+JSON

Removes one or more values from a JSON document at the specified paths.

Signature

JSON_REMOVE(json_doc, path[, path]...)

Parameters

ParameterTypeDescription
json_docJSONThe source JSON document
pathVARCHARJSONPath expression of the element to remove

Examples

Remove a key

sql
SELECT JSON_REMOVE('{"a":1,"b":2}', '$.b');
-- {"a": 1}

Remove array element by index

sql
SELECT JSON_REMOVE('[1,2,3]', '$[1]');
-- [1, 3]

Remove multiple sensitive keys

sql
SELECT JSON_REMOVE(data, '$.password', '$.secret') FROM users;
-- sanitised JSON

Conditional removal

sql
UPDATE profiles SET data = JSON_REMOVE(data, '$.tmp') WHERE JSON_CONTAINS_PATH(data, 'one', '$.tmp');
-- rows affected

Nested path removal

sql
SELECT JSON_REMOVE('{"a":{"b":1,"c":2}}', '$.a.c');
-- {"a": {"b": 1}}
Anti-PatternRemoving array elements by value

JSON_REMOVE only accepts path expressions (positions), not values — you cannot pass a value to remove.

✓ Instead: Use JSON_SEARCH to find the path of the value first, then pass that path to JSON_REMOVE.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses the #- operator (e.g. doc #- '{key}') or jsonb_delete_path() for jsonb.

JSON_MERGE

MariaDB 10.2+ (deprecated)JSON

Merges two or more JSON documents, combining duplicate keys into arrays (deprecated alias for JSON_MERGE_PRESERVE).

Signature

JSON_MERGE(json_doc, json_doc[, json_doc]...)

Parameters

ParameterTypeDescription
json_docJSONA JSON document to merge; two or more required

Examples

Non-overlapping keys merged

sql
SELECT JSON_MERGE('{"a":1}', '{"b":2}');
-- {"a": 1, "b": 2}

Duplicate keys combined into array

sql
SELECT JSON_MERGE('{"a":1}', '{"a":2}');
-- {"a": [1, 2]}

Arrays concatenated

sql
SELECT JSON_MERGE('[1,2]', '[3,4]');
-- [1, 2, 3, 4]

Three-way merge

sql
SELECT JSON_MERGE('{"a":1}', '{"a":2}', '{"a":3}');
-- {"a": [1, 2, 3]}

Column merge

sql
SELECT JSON_MERGE(base, overrides) FROM config_table;
-- merged JSON per row
Anti-PatternExpecting JSON_MERGE to overwrite duplicates

Unlike a typical dictionary merge, JSON_MERGE collects duplicate key values into an array rather than replacing them.

✓ Instead: Use JSON_MERGE_PATCH if you want last-writer-wins semantics on duplicate keys.

MariaDB Note

Deprecated alias — use JSON_MERGE_PRESERVE in new code.

⇄ vs MySQL: Available in MySQL 5.7+ but deprecated in MySQL 8.0; prefer JSON_MERGE_PRESERVE.
⇄ vs PostgreSQL: PostgreSQL uses the || operator on jsonb for shallow merge (last-writer-wins).

JSON_MERGE_PATCH

MariaDB 10.2.25+ / 10.3.15+ / 10.4.5+JSON

Merges JSON documents following RFC 7396 — duplicate keys in later documents overwrite earlier ones, and null values remove keys.

Signature

JSON_MERGE_PATCH(json_doc, json_doc[, json_doc]...)

Parameters

ParameterTypeDescription
json_docJSONA JSON document to merge; the rightmost document wins on conflicts

Examples

Later value wins for key b

sql
SELECT JSON_MERGE_PATCH('{"a":1,"b":2}', '{"b":99,"c":3}');
-- {"a": 1, "b": 99, "c": 3}

null patch value removes the key

sql
SELECT JSON_MERGE_PATCH('{"a":1,"b":2}', '{"b":null}');
-- {"a": 1}

Apply a partial update patch

sql
UPDATE settings SET config = JSON_MERGE_PATCH(config, ?) WHERE user_id = ?;
-- 1 row affected

Sequential patch application

sql
SELECT JSON_MERGE_PATCH(a, b, c) FROM patches ORDER BY version;
-- final merged doc

Nested objects replaced, not deep-merged

sql
SELECT JSON_MERGE_PATCH('{"x":{"y":1}}', '{"x":{"z":2}}');
-- {"x": {"z": 2}}
Anti-PatternExpecting deep recursive merging

JSON_MERGE_PATCH replaces nested objects wholesale rather than merging them recursively, so sibling keys in a nested object can be lost.

✓ Instead: Use JSON_MERGE_PRESERVE if you need deep array/object merging, or use JSON_SET for targeted key updates.

⇄ vs MySQL: Available in MySQL 8.0+ with the same RFC 7396 semantics.
⇄ vs PostgreSQL: PostgreSQL's || operator on jsonb does a shallow last-writer-wins merge; no native RFC 7396 implementation.

JSON_MERGE_PRESERVE

MariaDB 10.2.25+ / 10.3.15+ / 10.4.5+JSON

Merges two or more JSON documents, preserving all values for duplicate keys by combining them into arrays.

Signature

JSON_MERGE_PRESERVE(json_doc, json_doc[, json_doc]...)

Parameters

ParameterTypeDescription
json_docJSONA JSON document to merge; duplicate keys are collected into arrays

Examples

Duplicate keys combined into array

sql
SELECT JSON_MERGE_PRESERVE('{"a":1}', '{"a":2}');
-- {"a": [1, 2]}

Arrays concatenated

sql
SELECT JSON_MERGE_PRESERVE('{"tags":["a"]}', '{"tags":["b"]}');
-- {"tags": ["a", "b"]}

Distinct keys simply combined

sql
SELECT JSON_MERGE_PRESERVE('{"a":1}', '{"b":2}');
-- {"a": 1, "b": 2}

Join and merge

sql
SELECT JSON_MERGE_PRESERVE(r1.data, r2.data) FROM t AS r1 JOIN t AS r2 ON r1.id = r2.parent_id;
-- merged JSON

Three-way preserve

sql
SELECT JSON_MERGE_PRESERVE('{"a":1}', '{"a":2}', '{"a":3}');
-- {"a": [1, 2, 3]}
Anti-PatternUsing JSON_MERGE_PRESERVE when you want last-writer-wins

If you expect duplicate keys to be overwritten, JSON_MERGE_PRESERVE will silently wrap them in arrays instead.

✓ Instead: Use JSON_MERGE_PATCH for RFC 7396 last-writer-wins semantics.

⇄ vs MySQL: Available in MySQL 8.0+; replaces the deprecated JSON_MERGE().
⇄ vs PostgreSQL: PostgreSQL has no exact equivalent; the || operator on jsonb overwrites duplicates.

JSON_ARRAY

MariaDB 10.2+JSON

Creates a JSON array from the supplied list of values.

Signature

JSON_ARRAY([val[, val]...])

Parameters

ParameterTypeDescription
valANYZero or more values to include in the array; SQL NULL becomes JSON null

Examples

Numeric array

sql
SELECT JSON_ARRAY(1, 2, 3);
-- [1, 2, 3]

Mixed with JSON null

sql
SELECT JSON_ARRAY('a', 'b', NULL);
-- ["a", "b", null]

Row as array

sql
SELECT JSON_ARRAY(id, name, score) FROM leaderboard LIMIT 1;
-- [7, "Alice", 98]

Nested object in array

sql
SELECT JSON_ARRAY(JSON_OBJECT('k', v)) FROM t;
-- [{"k": "val"}]

Empty array

sql
SELECT JSON_ARRAY();
-- []
Anti-PatternWrapping an already-JSON value

Passing a JSON string literal to JSON_ARRAY treats it as a plain string and double-encodes it.

✓ Instead: Use JSON_EXTRACT or CAST to materialise the JSON value before passing it, or use JSON_MERGE to combine arrays.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses json_build_array() or the ARRAY[] constructor with to_json().

JSON_OBJECT

MariaDB 10.2+JSON

Creates a JSON object from alternating key-value pairs.

Signature

JSON_OBJECT([key, val[, key, val]...])

Parameters

ParameterTypeDescription
keyVARCHARString key for the JSON object property
valANYValue for the corresponding key; SQL NULL becomes JSON null

Examples

Basic object construction

sql
SELECT JSON_OBJECT('name', 'Alice', 'age', 30);
-- {"name": "Alice", "age": 30}

Build from columns

sql
SELECT JSON_OBJECT('id', id, 'email', email) FROM users WHERE id = 1;
-- {"id": 1, "email": "a@b.com"}

Nested array value

sql
SELECT JSON_OBJECT('data', JSON_ARRAY(1, 2, 3));
-- {"data": [1, 2, 3]}

Empty object

sql
SELECT JSON_OBJECT();
-- {}

Function values

sql
SELECT JSON_OBJECT('ts', NOW(), 'user', USER());
-- {"ts": "2024-...", "user": "root@..."}
Anti-PatternPassing an odd number of arguments

Passing an odd number of arguments causes an error because keys and values must be paired.

✓ Instead: Always supply key-value pairs; use NULL as a placeholder value if needed.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses json_build_object() with the same alternating key-value approach.

JSON_QUOTE

MariaDB 10.2+JSON

Wraps a string value in double quotes and escapes special characters to produce a valid JSON string literal.

Signature

JSON_QUOTE(string)

Parameters

ParameterTypeDescription
stringVARCHARThe string to encode as a JSON string literal

Examples

Basic quoting

sql
SELECT JSON_QUOTE('hello');
-- "hello"

Internal quotes escaped

sql
SELECT JSON_QUOTE('say "hi"');
-- "say \"hi\""

NULL passthrough

sql
SELECT JSON_QUOTE(NULL);
-- NULL

Quote a column value

sql
SELECT JSON_QUOTE(name) FROM products LIMIT 1;
-- "Widget Pro"

Embed newline-containing string safely

sql
SELECT JSON_SET('{}', '$.msg', JSON_QUOTE('line1\nline2'));
-- {"msg": "line1\nline2"}
Anti-PatternUsing JSON_QUOTE to produce a scalar number

JSON_QUOTE always produces a string literal; calling JSON_QUOTE(42) returns '"42"' (a string), not the number 42.

✓ Instead: Pass numeric values directly to JSON_SET or JSON_OBJECT without quoting.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses to_json() or quote_literal(); no JSON_QUOTE() function.

JSON_UNQUOTE

MariaDB 10.2+VARCHAR

Removes the outer JSON string quotes and unescapes escape sequences, returning a plain SQL string.

Signature

JSON_UNQUOTE(json_val)

Parameters

ParameterTypeDescription
json_valJSONA JSON string value (with surrounding double quotes) to unquote

Examples

Remove outer quotes

sql
SELECT JSON_UNQUOTE('"hello"');
-- hello

Common extract-then-unquote pattern

sql
SELECT JSON_UNQUOTE(JSON_EXTRACT('{"name":"Alice"}', '$.name'));
-- Alice

->> is shorthand for JSON_UNQUOTE(JSON_EXTRACT(...))

sql
SELECT col->>'$.name' FROM users;
-- Alice

Unescape sequences

sql
SELECT JSON_UNQUOTE('"line1\\nline2"');
-- line1\nline2

Use in WHERE-style comparison

sql
SELECT JSON_UNQUOTE(JSON_EXTRACT(data, '$.code')) = 'ABC' FROM orders;
-- 1 / 0
Anti-PatternApplying JSON_UNQUOTE to a non-string JSON value

Calling JSON_UNQUOTE on a JSON number or object just returns the value as-is — it only truly unquotes JSON strings.

✓ Instead: Use CAST or CONVERT when you need to coerce a JSON number or boolean to a SQL type.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL's ->> operator and json_extract_path_text() return unquoted text directly.

JSON_CONTAINS

MariaDB 10.2+INT (1 or 0)

Returns 1 if the target JSON document contains the candidate JSON value at the optional path.

Signature

JSON_CONTAINS(target, candidate[, path])

Parameters

ParameterTypeDescription
targetJSONThe JSON document to search within
candidateJSONThe JSON value to look for (must be valid JSON)
pathVARCHAROptional JSONPath to narrow the search to a sub-document

Examples

Scalar contained at path

sql
SELECT JSON_CONTAINS('{"a":1,"b":2}', '1', '$.a');
-- 1

Value in array

sql
SELECT JSON_CONTAINS('[1,2,3]', '2');
-- 1

Value not found

sql
SELECT JSON_CONTAINS('[1,2,3]', '5');
-- 0

Filter rows by tag

sql
SELECT * FROM products WHERE JSON_CONTAINS(tags, '"sale"');
-- matching rows

Object containment at path

sql
SELECT JSON_CONTAINS('{"a":{"b":1}}', '{"b":1}', '$.a');
-- 1
Anti-PatternPassing an unquoted string as the candidate

The candidate argument must be valid JSON — passing a bare string like 'sale' instead of '"sale"' causes an error.

✓ Instead: Always pass properly quoted JSON: '"sale"' for a string, '42' for a number, 'true' for a boolean.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses the @> containment operator on jsonb (e.g. doc @> '{"a":1}').

JSON_CONTAINS_PATH

MariaDB 10.2+INT (1 or 0)

Returns 1 if the JSON document contains data at any or all of the specified paths.

Signature

JSON_CONTAINS_PATH(json_doc, one_or_all, path[, path]...)

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to inspect
one_or_allVARCHAR'one' returns 1 if at least one path exists; 'all' requires all paths to exist
pathVARCHAROne or more JSONPath expressions to check

Examples

Path exists

sql
SELECT JSON_CONTAINS_PATH('{"a":1}', 'one', '$.a');
-- 1

Path absent

sql
SELECT JSON_CONTAINS_PATH('{"a":1}', 'one', '$.b');
-- 0

Check both keys exist

sql
SELECT JSON_CONTAINS_PATH(data, 'all', '$.name', '$.email') FROM users;
-- 1 or 0 per row

OR logic across two paths

sql
SELECT JSON_CONTAINS_PATH(data, 'one', '$.addr', '$.location') FROM contacts;
-- 1 if either key exists

Filter by key presence

sql
SELECT * FROM orders WHERE JSON_CONTAINS_PATH(meta, 'one', '$.discount');
-- rows with discount key
Anti-PatternUsing JSON_CONTAINS_PATH to check a value, not a path

JSON_CONTAINS_PATH only tests key/path existence, not whether the value at that path matches anything.

✓ Instead: Use JSON_CONTAINS to test for a specific value at a path.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses the ? operator for top-level key existence and jsonb_path_exists() for deeper paths.

JSON_KEYS

MariaDB 10.2+JSON (array of key names)

Returns the keys of the top-level object (or the object at the given path) as a JSON array of strings.

Signature

JSON_KEYS(json_doc[, path])

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to inspect
pathVARCHAROptional path to a nested object whose keys to return

Examples

Top-level keys

sql
SELECT JSON_KEYS('{"a":1,"b":2}');
-- ["a", "b"]

Nested object keys

sql
SELECT JSON_KEYS('{"a":{"x":1,"y":2}}', '$.a');
-- ["x", "y"]

Arrays return NULL

sql
SELECT JSON_KEYS('[1,2,3]');
-- NULL

Inspect document structure

sql
SELECT JSON_KEYS(data) FROM config WHERE id = 1;
-- ["host", "port", "db"]

Count top-level keys

sql
SELECT JSON_LENGTH(JSON_KEYS(data)) AS num_keys FROM config;
-- 3
Anti-PatternCalling JSON_KEYS on an array

JSON_KEYS returns NULL for JSON arrays — it only applies to JSON objects.

✓ Instead: Use JSON_LENGTH to count array elements or JSON_EXTRACT with $[*] to enumerate array values.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_object_keys() to return keys as a set of rows.

JSON_LENGTH

MariaDB 10.2+INT

Returns the number of elements in a JSON array, keys in a JSON object, or 1 for a scalar.

Signature

JSON_LENGTH(json_doc[, path])

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to measure
pathVARCHAROptional path to a sub-document to measure instead

Examples

Array length

sql
SELECT JSON_LENGTH('[1,2,3]');
-- 3

Object key count

sql
SELECT JSON_LENGTH('{"a":1,"b":2}');
-- 2

Scalar always returns 1

sql
SELECT JSON_LENGTH('"hello"');
-- 1

Length of nested array

sql
SELECT JSON_LENGTH(data, '$.tags') FROM posts;
-- 4

Filter by array size

sql
SELECT * FROM orders WHERE JSON_LENGTH(items) > 5;
-- large orders
Anti-PatternUsing JSON_LENGTH to check if a document is empty without guarding NULL

JSON_LENGTH('{}') returns 0 and JSON_LENGTH('[]') returns 0, but JSON_LENGTH(NULL) returns NULL — missing the NULL case leads to unexpected query results.

✓ Instead: Use COALESCE(JSON_LENGTH(col), 0) = 0 to treat NULL documents as empty.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_array_length() for arrays and jsonb_object_keys() count for objects.

JSON_DEPTH

MariaDB 10.2+INT

Returns the maximum nesting depth of a JSON document.

Signature

JSON_DEPTH(json_doc)

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to analyse

Examples

Empty object = depth 1

sql
SELECT JSON_DEPTH('{}');
-- 1

Array of scalars = depth 2

sql
SELECT JSON_DEPTH('[1,2,3]');
-- 2

Three levels of nesting

sql
SELECT JSON_DEPTH('{"a":{"b":{"c":1}}}');
-- 4

Scalar = depth 1

sql
SELECT JSON_DEPTH('42');
-- 1

Find most deeply nested documents

sql
SELECT id, JSON_DEPTH(data) FROM documents ORDER BY 2 DESC LIMIT 5;
-- deepest docs
Anti-PatternUsing JSON_DEPTH to count array elements

JSON_DEPTH measures nesting levels, not element count — a flat array of 1000 elements has depth 2.

✓ Instead: Use JSON_LENGTH to count elements.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL has no direct equivalent; use a recursive function or extension.

JSON_TYPE

MariaDB 10.2+VARCHAR

Returns a string indicating the JSON type of a value: OBJECT, ARRAY, STRING, INTEGER, DOUBLE, BOOLEAN, NULL, or others.

Signature

JSON_TYPE(json_val)

Parameters

ParameterTypeDescription
json_valJSONThe JSON value whose type to determine

Examples

Object type

sql
SELECT JSON_TYPE('{"a":1}');
-- OBJECT

Array type

sql
SELECT JSON_TYPE('[1,2]');
-- ARRAY

String type

sql
SELECT JSON_TYPE('"hello"');
-- STRING

Integer type

sql
SELECT JSON_TYPE('42');
-- INTEGER

Check extracted value type

sql
SELECT JSON_TYPE(JSON_EXTRACT(data, '$.score')) FROM results;
-- INTEGER or DOUBLE
Anti-PatternComparing JSON_TYPE result case-insensitively

JSON_TYPE returns uppercase strings; comparisons are case-sensitive in binary collation contexts.

✓ Instead: Always compare against the exact uppercase literals: 'OBJECT', 'ARRAY', 'STRING', 'INTEGER', 'DOUBLE', 'BOOLEAN', 'NULL'.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_typeof() which returns lowercase type names like 'object', 'array', 'string', 'number', 'boolean', 'null'.

JSON_VALID

MariaDB 10.2+INT (1 or 0)

Returns 1 if the argument is valid JSON, 0 if it is invalid, and NULL if the argument is NULL.

Signature

JSON_VALID(val)

Parameters

ParameterTypeDescription
valVARCHAR / JSONThe value to test for JSON validity

Examples

Valid JSON object

sql
SELECT JSON_VALID('{"a":1}');
-- 1

Valid JSON array

sql
SELECT JSON_VALID('[1,2,3]');
-- 1

Invalid JSON

sql
SELECT JSON_VALID('not json');
-- 0

NULL input returns NULL

sql
SELECT JSON_VALID(NULL);
-- NULL

Find rows with bad JSON

sql
SELECT * FROM raw_imports WHERE JSON_VALID(payload) = 0;
-- invalid rows
Anti-PatternSkipping JSON_VALID before parsing untrusted input

Calling JSON_EXTRACT or JSON_SET on invalid JSON causes an error that can crash stored procedures or application queries.

✓ Instead: Validate with JSON_VALID() first, especially for data ingested from external sources.

⇄ vs MySQL: Identical in MySQL 5.7+.
⇄ vs PostgreSQL: PostgreSQL validates JSON implicitly on cast to json/jsonb; use a TRY CAST or error-handling block for explicit validation.

JSON_DETAILED

MariaDB 10.2.4+LONGTEXT

Returns a human-readable, indented (pretty-printed) representation of a JSON document.

Signature

JSON_DETAILED(json_doc[, tab_size])

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to format
tab_sizeINTNumber of spaces per indent level (default 4)

Examples

Default 4-space indent

sql
SELECT JSON_DETAILED('{"a":1,"b":[1,2]}');
-- multi-line indented JSON

Custom indent width

sql
SELECT JSON_DETAILED(data, 2) FROM config WHERE id = 1;
-- 2-space indent

Format constructed object

sql
SELECT JSON_DETAILED(JSON_OBJECT('name','Alice','age',30));
-- pretty object

Nested structure

sql
SELECT JSON_DETAILED('[1,[2,3],{"x":4}]');
-- nested indented

Useful in mysql CLI with \G

sql
SELECT JSON_DETAILED(col) FROM t LIMIT 1 \G
-- readable in terminal
Anti-PatternStoring JSON_DETAILED output back into the column

The extra whitespace inflates storage size significantly for large documents.

✓ Instead: Use JSON_DETAILED only for display/debugging; store and transmit compact JSON.

MariaDB Note

MariaDB-specific function; MySQL equivalent is JSON_PRETTY().

⇄ vs MySQL: MySQL uses JSON_PRETTY() instead; JSON_DETAILED() is MariaDB-specific.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_pretty() for indented output.

JSON_COMPACT

MariaDB 10.2.4+LONGTEXT

Returns a compact representation of a JSON document with all unnecessary whitespace removed.

Signature

JSON_COMPACT(json_doc)

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to compact

Examples

Whitespace stripped

sql
SELECT JSON_COMPACT('{  "a" :  1 ,  "b" :  2  }');
-- {"a":1,"b":2}

Round-trip pretty then compact

sql
SELECT JSON_COMPACT(JSON_DETAILED(data)) FROM config;
-- original compact form

Normalise stored JSON in-place

sql
UPDATE docs SET content = JSON_COMPACT(content) WHERE JSON_LENGTH(content) > 0;
-- rows updated

Measure compacted size

sql
SELECT CHAR_LENGTH(JSON_COMPACT(data)) AS compact_size FROM t;
-- byte count

Array compaction

sql
SELECT JSON_COMPACT(' [ 1 , 2 , 3 ] ');
-- [1,2,3]
Anti-PatternApplying JSON_COMPACT repeatedly on reads in a hot query path

JSON_COMPACT parses and re-serialises the document on every call, adding CPU overhead that is unnecessary if the data is already compact.

✓ Instead: Compact data once at write time and store it; avoid repeated compaction on reads.

MariaDB Note

MariaDB-specific function with no MySQL equivalent.

⇄ vs MySQL: MySQL has no JSON_COMPACT(); this is a MariaDB-specific function.
⇄ vs PostgreSQL: PostgreSQL jsonb type is always stored in a normalised binary form; no explicit compact function needed.

JSON_LOOSE

MariaDB 10.2.4+LONGTEXT

Returns a loosely formatted JSON document with spaces after colons and commas for light readability.

Signature

JSON_LOOSE(json_doc)

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to format loosely

Examples

Space after colon and comma

sql
SELECT JSON_LOOSE('{"a":1,"b":2}');
-- {"a": 1, "b": 2}

Spaced array

sql
SELECT JSON_LOOSE('[1,2,3]');
-- [1, 2, 3]

From JSON_OBJECT

sql
SELECT JSON_LOOSE(JSON_OBJECT('x', 1, 'y', 2));
-- {"x": 1, "y": 2}

Column formatting

sql
SELECT JSON_LOOSE(data) FROM settings WHERE id = 1;
-- lightly formatted JSON

Nested structure

sql
SELECT JSON_LOOSE('{"arr":[1,2,{"k":"v"}]}');
-- {"arr": [1, 2, {"k": "v"}]}
Anti-PatternConfusing JSON_LOOSE with JSON_DETAILED for deep debugging

JSON_LOOSE adds spaces but does not add newlines or indentation — deeply nested structures remain on one long line.

✓ Instead: Use JSON_DETAILED when you need full indented multi-line output for inspection.

MariaDB Note

MariaDB-specific function with no MySQL equivalent.

⇄ vs MySQL: MySQL has no JSON_LOOSE(); this is a MariaDB-specific function.
⇄ vs PostgreSQL: PostgreSQL has no equivalent; jsonb output is either compact or pretty (jsonb_pretty()).

JSON_PRETTY

MariaDB 10.5.9+ / 10.6.1+LONGTEXT

Returns a pretty-printed JSON document with newlines and indentation, providing MySQL-compatible pretty output.

Signature

JSON_PRETTY(json_doc)

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to pretty-print

Examples

Standard pretty-print

sql
SELECT JSON_PRETTY('{"a":1,"b":[1,2]}');
-- indented multi-line JSON

Pretty print a column

sql
SELECT JSON_PRETTY(data) FROM config WHERE id = 1;
-- human-readable config

Pretty-print constructed object

sql
SELECT JSON_PRETTY(JSON_OBJECT('host','localhost','port',5432));
-- indented object

NULL passthrough

sql
SELECT JSON_PRETTY(NULL);
-- NULL

Array of objects

sql
SELECT JSON_PRETTY('[{"id":1},{"id":2}]');
-- array of objects, indented
Anti-PatternStoring JSON_PRETTY output in application variables for large result sets

Storing pretty-printed JSON in application memory bloats RAM usage for large result sets.

✓ Instead: Apply JSON_PRETTY only in the final display layer; operate on compact JSON internally.

MariaDB Note

Functionally equivalent to JSON_DETAILED with default indent; added for MySQL compatibility.

⇄ vs MySQL: Available in MySQL 8.0+ with the same signature — use JSON_PRETTY for cross-compatibility.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_pretty() on jsonb values.

JSON_ARRAYAGG

MariaDB 10.5.0+JSON

Aggregate function that collects values from multiple rows into a single JSON array.

Signature

JSON_ARRAYAGG(val [ORDER BY col])

Parameters

ParameterTypeDescription
valANYThe expression or column to collect into the JSON array

Examples

Collect all names

sql
SELECT JSON_ARRAYAGG(name) FROM users;
-- ["Alice","Bob","Carol"]

Grouped and ordered

sql
SELECT dept_id, JSON_ARRAYAGG(name ORDER BY name) FROM employees GROUP BY dept_id;
-- per-department sorted arrays

Distinct values only

sql
SELECT JSON_ARRAYAGG(DISTINCT status) FROM orders;
-- ["pending","shipped"]

Array of objects

sql
SELECT JSON_ARRAYAGG(JSON_OBJECT('id',id,'name',name)) FROM products;
-- [{"id":1,"name":"..."},...]

Ordered numeric array

sql
SELECT JSON_ARRAYAGG(score ORDER BY score DESC) FROM scores WHERE user_id = 1;
-- [98, 87, 75]
Anti-PatternUsing JSON_ARRAYAGG without GROUP BY on large tables

Without GROUP BY, JSON_ARRAYAGG collects every row in the table into one array in memory, which can exhaust server RAM.

✓ Instead: Always pair with GROUP BY or use LIMIT / a subquery to cap the input set.

⇄ vs MySQL: Available in MySQL 5.7.22+ with the same syntax.
⇄ vs PostgreSQL: PostgreSQL uses json_agg() or jsonb_agg(); ordering uses json_agg(val ORDER BY col).

JSON_OBJECTAGG

MariaDB 10.5.0+JSON

Aggregate function that builds a JSON object from key-value pairs across multiple rows.

Signature

JSON_OBJECTAGG(key, value)

Parameters

ParameterTypeDescription
keyVARCHARExpression used as the JSON object key (should be unique per group)
valueANYExpression used as the corresponding JSON object value

Examples

Key-value pairs from rows

sql
SELECT JSON_OBJECTAGG(code, value) FROM settings;
-- {"theme":"dark","lang":"en"}

Grouped pivot

sql
SELECT dept, JSON_OBJECTAGG(name, salary) FROM staff GROUP BY dept;
-- per-dept salary map

Nested object values

sql
SELECT JSON_OBJECTAGG(id, JSON_OBJECT('name',name,'age',age)) FROM users;
-- {"1":{"name":"Alice",...}}

Build translation map

sql
SELECT JSON_OBJECTAGG(lang, translation) FROM i18n WHERE key = 'greeting';
-- {"en":"Hello","fr":"Bonjour"}

Daily KPI map

sql
SELECT JSON_OBJECTAGG(metric, val) FROM kpis WHERE date = CURDATE();
-- {"revenue":5000,"orders":42}
Anti-PatternAssuming duplicate keys are handled deterministically

If multiple rows produce the same key, JSON_OBJECTAGG produces a document with duplicate keys — the behaviour is undefined and results vary by version.

✓ Instead: Ensure keys are unique before aggregation, using DISTINCT on the key expression or pre-filtering.

⇄ vs MySQL: Available in MySQL 5.7.22+ with the same syntax.
⇄ vs PostgreSQL: PostgreSQL uses json_object_agg(key, value) or jsonb_object_agg(key, value).

JSON_TABLE

MariaDB 10.6+TABLE (relational rowset)

Shreds a JSON document into a relational table, mapping JSON paths to columns with optional type coercion and error handling.

Signature

JSON_TABLE(json_doc, path COLUMNS (col_def[, col_def]...)) [AS] alias

Parameters

ParameterTypeDescription
json_docJSONThe JSON document or column to shred
pathVARCHARJSONPath to the array element to iterate over (e.g. '$[*]')
col_defkeywordColumn definitions using FOR ORDINALITY, PATH, EXISTS PATH, or NESTED PATH clauses

Examples

Array of objects to rows

sql
SELECT * FROM JSON_TABLE('[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]', '$[*]' COLUMNS(id INT PATH '$.id', name VARCHAR(50) PATH '$.name')) AS jt;
-- id | name -- 1 | Alice -- 2 | Bob

Lateral join to explode order items

sql
SELECT jt.* FROM orders, JSON_TABLE(orders.items, '$[*]' COLUMNS(sku VARCHAR(20) PATH '$.sku', qty INT PATH '$.qty')) AS jt;
-- one row per order item

Scalar array to rows

sql
SELECT n FROM JSON_TABLE('[1,2,3,4,5]', '$[*]' COLUMNS(n INT PATH '
    
  

)) AS jt;
-- 1 2 3 4 5

With row ordinal number

sql
SELECT * FROM JSON_TABLE(data, '$[*]' COLUMNS(ord FOR ORDINALITY, v VARCHAR(100) PATH '
    
  

)) AS jt;
-- ord | v

Extract nested array

sql
SELECT * FROM JSON_TABLE('{"users":[{"id":1},{"id":2}]}', '$.users[*]' COLUMNS(id INT PATH '$.id')) AS jt;
-- 1 2
Anti-PatternUsing JSON_TABLE on a non-array path

If the path expression does not point to an array, JSON_TABLE returns at most one row, which is often not what is intended.

✓ Instead: Ensure the path ends with [*] or points to a JSON array; use JSON_TYPE to verify beforehand.

MariaDB Note

Introduced in MariaDB 10.6 following the SQL/JSON standard.

⇄ vs MySQL: Available in MySQL 8.0+ with the same SQL/JSON standard syntax.
⇄ vs PostgreSQL: PostgreSQL uses json_to_recordset() or jsonb_to_recordset() for similar array-to-rows conversion.

JSON_EXISTS

MariaDB 10.6.1+INT (1 or 0)

Returns 1 if the SQL/JSON path expression identifies any value in the JSON document, 0 otherwise.

Signature

JSON_EXISTS(json_doc, path [on_error])

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to query
pathVARCHARSQL/JSON path expression to check for existence
on_errorkeywordOptional: TRUE ON ERROR, FALSE ON ERROR, or UNKNOWN ON ERROR (default UNKNOWN)

Examples

Key exists

sql
SELECT JSON_EXISTS('{"a":1}', '$.a');
-- 1

Key absent

sql
SELECT JSON_EXISTS('{"a":1}', '$.b');
-- 0

Filter by key presence

sql
SELECT * FROM orders WHERE JSON_EXISTS(meta, '$.promo_code');
-- orders with promo

Out-of-bounds index

sql
SELECT JSON_EXISTS('[1,2,3]', '$[5]');
-- 0

Strict mode with error handling

sql
SELECT JSON_EXISTS(data, 'strict $.required_field' FALSE ON ERROR) FROM docs;
-- 0 on invalid JSON
Anti-PatternUsing JSON_EXISTS on MariaDB versions before 10.6.1

JSON_EXISTS is only available from MariaDB 10.6.1+; using it on earlier versions causes a function-not-found error.

✓ Instead: Use JSON_CONTAINS_PATH for compatibility with MariaDB 10.2–10.5 installations.

MariaDB Note

MariaDB-specific implementation of the SQL/JSON standard JSON_EXISTS predicate.

⇄ vs MySQL: Not available in MySQL; MySQL uses JSON_CONTAINS_PATH() instead.
⇄ vs PostgreSQL: PostgreSQL uses jsonb_path_exists() with the same SQL/JSON standard path syntax.

JSON_SCHEMA_VALID

MariaDB 10.6.1+INT (1 or 0)

Validates a JSON document against a JSON Schema (draft-07) and returns 1 if valid, 0 otherwise.

Signature

JSON_SCHEMA_VALID(schema, document)

Parameters

ParameterTypeDescription
schemaJSONA valid JSON Schema document (draft-07) describing the expected structure
documentJSONThe JSON document to validate against the schema

Examples

Required property present

sql
SELECT JSON_SCHEMA_VALID('{"type":"object","required":["name"]}', '{"name":"Alice"}');
-- 1

Required property missing

sql
SELECT JSON_SCHEMA_VALID('{"type":"object","required":["name"]}', '{"age":30}');
-- 0

Scalar validation

sql
SELECT JSON_SCHEMA_VALID('{"type":"integer","minimum":0}', '42');
-- 1

Enforce schema at DDL level

sql
ALTER TABLE orders ADD CONSTRAINT chk_meta CHECK (JSON_SCHEMA_VALID('{"type":"object"}', meta));
-- constraint added

Find schema-violating rows

sql
SELECT id FROM uploads WHERE JSON_SCHEMA_VALID(@schema, payload) = 0;
-- invalid rows
Anti-PatternUsing very large or deeply recursive schemas

Highly complex JSON Schema documents with $ref cycles or allOf/anyOf chains can cause significant validation overhead per row.

✓ Instead: Keep schemas focused on essential constraints and validate complex business rules in the application layer.

⇄ vs MySQL: Available in MySQL 8.0.17+ with the same JSON Schema draft-07 support.
⇄ vs PostgreSQL: PostgreSQL has no built-in JSON Schema validation; use the pg_jsonschema extension.

JSON_NORMALIZE

MariaDB 10.7+LONGTEXT

Returns a normalised canonical form of a JSON document: keys sorted alphabetically, whitespace removed, and number representation standardised.

Signature

JSON_NORMALIZE(json_doc)

Parameters

ParameterTypeDescription
json_docJSONThe JSON document to normalise

Examples

Keys sorted alphabetically

sql
SELECT JSON_NORMALIZE('{"b":2,"a":1}');
-- {"a":1,"b":2}

Whitespace removed

sql
SELECT JSON_NORMALIZE('{ "a" :  1 }');
-- {"a":1}

Number normalisation (1.0 becomes 1)

sql
SELECT JSON_NORMALIZE('{"n":1.0}');
-- {"n":1}

Semantic equality check

sql
SELECT JSON_NORMALIZE(a) = JSON_NORMALIZE(b) AS equal FROM comparisons;
-- 1 or 0

Generate stable content hash

sql
SELECT MD5(JSON_NORMALIZE(data)) AS fingerprint FROM configs;
-- stable hash
Anti-PatternRelying on JSON_NORMALIZE to sort array elements

JSON_NORMALIZE sorts object keys but does not sort array elements — two arrays with the same elements in different order will not normalise to the same string.

✓ Instead: Sort array contents in the application layer before storing if order-independent array equality is required.

MariaDB Note

MariaDB-specific function; particularly useful for content-addressable caching and change detection.

⇄ vs MySQL: Not available in MySQL; this is a MariaDB-specific function.
⇄ vs PostgreSQL: PostgreSQL jsonb type automatically normalises key order and number representation on storage.