JSON 함수
JSON 함수 (JSON Functions)
Turso는 SQLite JSON1 확장과 호환되는 JSON 함수들을 모두 갖추고 있어요. 이 함수들은 TEXT로 저장된 JSON이나 Turso의 내부 이진 JSON(JSONB) 형식으로 저장된 JSON을 다뤄요.
출처: 문서
본문
함수 대부분이 짝을 이루고 있어요: TEXT를 반환하는 json_* 변형과 내부 이진 형식의 BLOB을 반환하는 jsonb_* 변형이 그거예요. JSONB 변형은 결과를 애플리케이션에 돌려주는 게 아니라 저장하거나 다른 JSON 함수에 넘길 때 더 효율적이에요.
JSON 경로 문법
많은 JSON 함수가 JSON 문서 안의 특정 원소를 가리키는 경로(path) 인수를 받아요.
| 문법 | 의미 |
|---|---|
$ |
루트 원소 |
$.key |
key라는 이름의 객체 멤버 |
$[N] |
인덱스 N의 배열 원소 (0부터 시작) |
$.key1.key2 |
중첩된 객체 멤버 |
$.key[0] |
객체 멤버 안 배열의 첫 번째 원소 |
$[0].key |
첫 번째 배열 원소 안의 객체 멤버 |
경로 인수는 반드시 $로 시작해야 해요. 경로가 어떤 원소와도 일치하지 않으면 함수는 보통 NULL을 반환해요.
SELECT json_extract('{"a": {"b": [10, 20, 30]}}', '$.a.b[1]');
-- 20
JSON 생성과 검증
json
JSON 문자열을 검증하고 압축(minified) 형태로 반환해요. 입력이 유효한 JSON이 아니면 오류가 발생해요.
json(json_text)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT | 검증하고 압축할 JSON 문자열 |
반환: TEXT -- 압축된 JSON 문자열.
SELECT json(' { "name": "Alice" , "age": 30 } ');
-- {"name":"Alice","age":30}
jsonb
JSON 문자열을 내부 이진 JSON 형식으로 바꿔요.
jsonb(json_text)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT | 변환할 JSON 문자열 |
반환: BLOB -- 이진 JSON 형식의 값.
SELECT typeof(jsonb('{"a":1}'));
-- blob
json_array / jsonb_array
인수들로 JSON 배열을 만들어요.
json_array(value1, value2, ...)
jsonb_array(value1, value2, ...)
| 매개변수 | 타입 | 설명 |
|---|---|---|
value1, value2, ... |
any | 배열에 넣을 값. SQL NULL은 JSON null이 돼요. |
반환: TEXT (json_array) 또는 BLOB (jsonb_array) -- JSON 배열.
SELECT json_array(1, 'hello', NULL, 3.14);
-- [1,"hello",null,3.14]
SELECT json_array();
-- []
json_object / jsonb_object
label/value 쌍을 번갈아 나열해서 JSON 객체를 만들어요. *로 호출하면 그 행의 모든 컬럼을 컬럼 이름을 키로 써서 label/value 쌍으로 펼쳐요.
json_object(label1, value1, label2, value2, ...)
jsonb_object(label1, value1, label2, value2, ...)
json_object(*)
| 매개변수 | 타입 | 설명 |
|---|---|---|
label1, label2, ... |
TEXT | JSON 객체의 키. 반드시 문자열이어야 해요. |
value1, value2, ... |
any | 대응하는 값. SQL NULL은 JSON null이 돼요. |
반환: TEXT (json_object) 또는 BLOB (jsonb_object) -- JSON 객체.
SELECT json_object('name', 'Alice', 'age', 30);
-- {"name":"Alice","age":30}
SELECT json_object('items', json_array(1, 2, 3));
-- {"items":[1,2,3]}
json_quote
SQL 값을 JSON 표현으로 바꿔요.
json_quote(value)
| 매개변수 | 타입 | 설명 |
|---|---|---|
value |
any | JSON으로 인용할 SQL 값 |
반환: TEXT -- 값의 JSON 표현.
SELECT json_quote('hello');
-- "hello"
SELECT json_quote(42);
-- 42
SELECT json_quote(NULL);
-- null
json_valid
인수가 올바른 형태의 JSON이면 1, 아니면 0을 반환해요.
json_valid(json_text)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT | 유효한 JSON인지 검사할 문자열 |
반환: INTEGER -- 유효한 JSON이면 1, 아니면 0.
SELECT json_valid('{"name":"Alice"}');
-- 1
SELECT json_valid('not json');
-- 0
SELECT json_valid(NULL);
-- 0
json_error_position
JSON 문자열에서 첫 번째 문법 오류의 문자 위치를 반환하고, 문자열이 유효한 JSON이면 0을 반환해요.
json_error_position(json_text)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT | JSON 오류를 검사할 문자열 |
반환: INTEGER -- 첫 번째 오류의 문자 위치 (1부터 시작), 유효하면 0.
SELECT json_error_position('{"a":1}');
-- 0
SELECT json_error_position('{"a":}');
-- 6
JSON 추출
json_extract / jsonb_extract
경로 인수로 JSON 문서에서 하나 이상의 값을 꺼내요.
json_extract(json_text, path)
json_extract(json_text, path1, path2, ...)
jsonb_extract(json_text, path)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | JSON 경로 표현식 하나 이상 |
반환: 경로가 하나면, 꺼낸 값을 자연스러운 SQL 타입(INTEGER, REAL, TEXT, NULL)으로 반환해요. JSON 객체와 배열은 TEXT로 반환돼요. 경로가 여러 개면, 꺼낸 값들을 JSON 배열로 반환해요. jsonb_extract는 BLOB을 반환해요.
SELECT json_extract('{"name":"Alice","age":30}', '$.name');
-- Alice
SELECT json_extract('{"name":"Alice","age":30}', '$.age');
-- 30
-- Multiple paths return a JSON array
SELECT json_extract('{"a":1,"b":2,"c":3}', '$.a', '$.c');
-- [1,3]
-> 연산자
JSON에서 값을 꺼내서 JSON으로 반환해요. 항상 JSON 텍스트를 반환하는 json_extract의 축약형이에요 (객체와 배열은 JSON 그대로, 문자열은 JSON 인용을 붙여서).
json_text -> path
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | JSON 경로 표현식 |
반환: TEXT -- JSON 형태로 꺼낸 값.
SELECT '{"name":"Alice"}' -> '$.name';
-- "Alice"
SELECT '{"items":[1,2,3]}' -> '$.items';
-- [1,2,3]
->> 연산자
JSON에서 값을 꺼내서 SQL 값으로 반환해요. 문자열은 인용이 벗겨지고, 숫자는 INTEGER 또는 REAL로, 불리언은 정수(0 또는 1)로 반환돼요.
json_text ->> path
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | JSON 경로 표현식 |
반환: 자연스러운 SQL 타입(TEXT, INTEGER, REAL, NULL)으로 꺼낸 값.
SELECT '{"name":"Alice"}' ->> '$.name';
-- Alice
SELECT '{"count":42}' ->> '$.count';
-- 42
json_type
JSON 값의 타입을 문자열로 반환해요: "null", "true", "false", "integer", "real", "text", "array", "object".
json_type(json_text)
json_type(json_text, path)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 선택. 들여다볼 JSON 경로. 생략하면 루트를 검사해요. |
반환: TEXT -- JSON 타입 이름.
SELECT json_type('{"a":1}');
-- object
SELECT json_type('[1, 2, 3]');
-- array
SELECT json_type('{"a": 1}', '$.a');
-- integer
SELECT json_type('{"a": "hello"}', '$.a');
-- text
JSON 수정
json_insert / jsonb_insert
JSON 문서에 새 값을 넣어요. 기존 값은 덮어쓰지 않아요. 경로가 이미 존재하면 값은 그대로 유지돼요.
json_insert(json_text, path1, value1, path2, value2, ...)
jsonb_insert(json_text, path1, value1, path2, value2, ...)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 값을 넣을 JSON 경로 |
value |
any | 넣을 값 |
반환: TEXT (json_insert) 또는 BLOB (jsonb_insert) -- 수정된 JSON.
SELECT json_insert('{"a":1}', '$.b', 2);
-- {"a":1,"b":2}
-- Existing values are NOT overwritten
SELECT json_insert('{"a":1}', '$.a', 99);
-- {"a":1}
json_replace / jsonb_replace
JSON 문서의 기존 값을 바꿔요. 경로가 존재하지 않으면 아무것도 넣지 않아요.
json_replace(json_text, path1, value1, path2, value2, ...)
jsonb_replace(json_text, path1, value1, path2, value2, ...)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 바꿀 값을 가리키는 JSON 경로 |
value |
any | 교체할 값 |
반환: TEXT (json_replace) 또는 BLOB (jsonb_replace) -- 수정된 JSON.
SELECT json_replace('{"a":1,"b":2}', '$.a', 99);
-- {"a":99,"b":2}
-- Non-existent paths are ignored
SELECT json_replace('{"a":1}', '$.b', 2);
-- {"a":1}
json_set / jsonb_set
JSON 문서에 값을 넣거나 바꿔요. json_insert와 json_replace의 동작을 합친 거예요: 경로가 있으면 값을 바꾸고, 없으면 값을 넣어요.
json_set(json_text, path1, value1, path2, value2, ...)
jsonb_set(json_text, path1, value1, path2, value2, ...)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 값을 가리키는 JSON 경로 |
value |
any | 설정할 값 |
반환: TEXT (json_set) 또는 BLOB (jsonb_set) -- 수정된 JSON.
-- Replace existing
SELECT json_set('{"a":1}', '$.a', 99);
-- {"a":99}
-- Insert new
SELECT json_set('{"a":1}', '$.b', 2);
-- {"a":1,"b":2}
json_remove / jsonb_remove
JSON 문서에서 하나 이상의 원소를 제거해요.
json_remove(json_text, path1, path2, ...)
jsonb_remove(json_text, path1, path2, ...)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 제거할 JSON 경로 하나 이상 |
반환: TEXT (json_remove) 또는 BLOB (jsonb_remove) -- 수정된 JSON.
SELECT json_remove('{"a":1,"b":2,"c":3}', '$.b');
-- {"a":1,"c":3}
SELECT json_remove('[1,2,3,4]', '$[1]');
-- [1,3,4]
json_patch / jsonb_patch
JSON 문서에 RFC 7396 병합 패치(merge patch)를 적용해요. 패치의 객체 멤버는 대상의 멤버를 덮어써요. 패치의 null 값은 해당 멤버를 제거해요.
json_patch(json_text, patch)
jsonb_patch(json_text, patch)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | 대상 JSON 문서 |
patch |
TEXT or BLOB | 적용할 병합 패치 |
반환: TEXT (json_patch) 또는 BLOB (jsonb_patch) -- 패치가 적용된 JSON.
SELECT json_patch('{"a":1,"b":2}', '{"b":3,"c":4}');
-- {"a":1,"b":3,"c":4}
-- null in the patch removes a key
SELECT json_patch('{"a":1,"b":2}', '{"b":null}');
-- {"a":1}
json_pretty
JSON 문서를 보기 좋게 출력한(들여쓰기된) 표현을 반환해요.
json_pretty(json_text)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
반환: TEXT -- 들여쓰기가 적용된 포맷된 JSON 문자열.
SELECT json_pretty('{"name":"Alice","scores":[90,85,92]}');
/*
{
"name": "Alice",
"scores": [
90,
85,
92
]
}
*/
JSON 배열 함수
json_array_length
JSON 배열의 원소 개수를 반환해요. 빈 배열이면 0, 배열이 아닌 JSON 값이면 NULL을 반환해요.
json_array_length(json_text)
json_array_length(json_text, path)
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 선택. 문서 안에서 배열을 가리키는 JSON 경로. |
반환: INTEGER -- 원소 개수, 경로의 값이 배열이 아니면 NULL.
SELECT json_array_length('[1, 2, 3, 4]');
-- 4
SELECT json_array_length('{"items": [10, 20]}', '$.items');
-- 2
SELECT json_array_length('{"a": 1}');
-- NULL
JSON 집계 함수
json_group_array / jsonb_group_array
그룹의 값들을 모아서 JSON 배열로 만드는 집계 함수예요.
json_group_array(value)
jsonb_group_array(value)
| 매개변수 | 타입 | 설명 |
|---|---|---|
value |
any | 각 행에서 모을 값 |
반환: TEXT (json_group_array) 또는 BLOB (jsonb_group_array) -- 그룹의 모든 값을 담은 JSON 배열.
CREATE TABLE items (category TEXT, name TEXT);
INSERT INTO items VALUES ('fruit', 'apple'), ('fruit', 'banana'), ('veggie', 'carrot');
SELECT category, json_group_array(name) FROM items GROUP BY category;
-- fruit | ["apple","banana"]
-- veggie | ["carrot"]
json_group_object / jsonb_group_object
그룹의 label/value 쌍을 모아서 JSON 객체로 만드는 집계 함수예요.
json_group_object(label, value)
jsonb_group_object(label, value)
| 매개변수 | 타입 | 설명 |
|---|---|---|
label |
TEXT | 각 항목의 키 |
value |
any | 각 항목의 값 |
반환: TEXT (json_group_object) 또는 BLOB (jsonb_group_object) -- JSON 객체.
CREATE TABLE settings (key TEXT, value TEXT);
INSERT INTO settings VALUES ('theme', 'dark'), ('lang', 'en');
SELECT json_group_object(key, value) FROM settings;
-- {"theme":"dark","lang":"en"}
JSON 테이블 값 함수
json_each
JSON 배열이나 객체의 최상위 원소들을 훑으면서 원소마다 한 행을 반환하는 테이블 값 함수예요.
SELECT * FROM json_each(json_text);
SELECT * FROM json_each(json_text, path);
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 선택. 순회할 배열이나 객체를 가리키는 JSON 경로. 기본값은 $. |
출력 컬럼:
| 컬럼 | 타입 | 설명 |
|---|---|---|
key |
TEXT or INTEGER | 객체 키(TEXT) 또는 배열 인덱스(INTEGER) |
value |
any | 원소의 값. 객체/배열은 JSON 텍스트로, 원시값은 SQL 값으로 |
type |
TEXT | JSON 타입: null, true, false, integer, real, text, array, object |
atom |
any | 원시값의 SQL 값 (배열과 객체는 NULL) |
id |
INTEGER | 원소의 순차 식별자 |
parent |
INTEGER | 부모 원소의 id (최상위는 NULL) |
fullkey |
TEXT | 이 원소까지의 전체 JSON 경로 |
path |
TEXT | 이 원소의 부모를 가리키는 JSON 경로 |
SELECT key, value, type FROM json_each('[10, "hello", null]');
-- 0 | 10 | integer
-- 1 | hello | text
-- 2 | null | null
SELECT key, value FROM json_each('{"a":1, "b":2}');
-- a | 1
-- b | 2
-- With a path
SELECT key, value FROM json_each('{"data": [1, 2, 3]}', '$.data');
-- 0 | 1
-- 1 | 2
-- 2 | 3
json_tree
`json_tree`는 부분 지원이에요. 일부 고급 순회 기능은 기대대로 동작하지 않을 수 있어요.JSON 문서를 재귀적으로 훑으면서, 모든 중첩 수준의 모든 원소마다 한 행을 반환하는 테이블 값 함수예요.
SELECT * FROM json_tree(json_text);
SELECT * FROM json_tree(json_text, path);
| 매개변수 | 타입 | 설명 |
|---|---|---|
json_text |
TEXT or BLOB | JSON 문서 |
path |
TEXT | 선택. 훑을 하위 트리를 가리키는 JSON 경로. 기본값은 $. |
출력 컬럼: json_each와 같아요.
SELECT key, value, type, path FROM json_tree('{"a": [1, 2]}');
-- NULL | {"a":[1,2]} | object | $
-- a | [1,2] | array | $
-- 0 | 1 | integer | $.a
-- 1 | 2 | integer | $.a
실전 예제
JSON 데이터 저장하고 조회하기
CREATE TABLE events (id INTEGER PRIMARY KEY, data TEXT);
INSERT INTO events VALUES (1, '{"type":"click","x":100,"y":200}');
INSERT INTO events VALUES (2, '{"type":"scroll","offset":500}');
-- Extract specific fields
SELECT id, data ->> '$.type' AS event_type FROM events;
-- 1 | click
-- 2 | scroll
-- Filter by JSON value
SELECT * FROM events WHERE data ->> '$.type' = 'click';
JSON 제자리에서 수정하기
UPDATE events
SET data = json_set(data, '$.timestamp', '2025-01-15T10:30:00Z')
WHERE id = 1;
SELECT data FROM events WHERE id = 1;
-- {"type":"click","x":100,"y":200,"timestamp":"2025-01-15T10:30:00Z"}
관계형 데이터로 JSON 만들기
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);
INSERT INTO users VALUES (1, 'Alice', '[email protected]');
INSERT INTO users VALUES (2, 'Bob', '[email protected]');
SELECT json_object('users', json_group_array(
json_object('id', id, 'name', name, 'email', email)
)) FROM users;
-- {"users":[{"id":1,"name":"Alice","email":"[email protected]"},{"id":2,"name":"Bob","email":"[email protected]"}]}
json_each로 JSON 배열 펼치기
CREATE TABLE orders (id INTEGER PRIMARY KEY, items TEXT);
INSERT INTO orders VALUES (1, '["widget","gadget","gizmo"]');
INSERT INTO orders VALUES (2, '["sprocket"]');
-- Expand each order's items into individual rows
SELECT orders.id, each.value AS item
FROM orders, json_each(orders.items) AS each;
-- 1 | widget
-- 1 | gadget
-- 1 | gizmo
-- 2 | sprocket