SQLite 확장으로 Tcl과 JSON 연결하기

SQLite 확장으로 Tcl과 JSON 연결하기 (Bridging Tcl and JSON with the SQLite Extension)

Tcl SQLite 확장은 Tcl과 JSON 사이의 임피던스 부정합(impedance mismatch)을 완화하는 브리지 역할을 할 수 있어요. 파싱과 이스케이프 처리의 대부분을 Tcl에서 SQLite로 거의 노력 없이 넘길 수 있어요. 이 문서는 SQLite를 통해 JSON을 소비(consume)하고 생성(emit)하는 기법을 보여줘요.

출처: 문서

본문

1. SQLite로 Tcl과 JSON 연결하기

Tcl SQLite 확장은 Tcl과 JSON 사이의 임피던스 부정합을 완화하는 브리지 역할을 할 수 있어요. 파싱과 이스케이프 처리를 Tcl에서 SQLite로 거의 노력 없이 모두 옮겨요. 아래 절들은 SQLite를 통해 JSON을 소비하고 생성하는 기법을 보여줘요.

1.1. JSON 소비하기

JSON 페이로드를 소비하고 분해하는 가장 간단한 방법 중 하나는 그것을 SQLite에 넣고 Tcl의 lassign 함수로 결과를 추출하는 거예요:

set json theJSON...
lassign [db eval {
  with payload(j) as (select jsonb($json)) select
  -- A simple value:
  j->>'foo',
  -- A sub-object or array:
  CASE WHEN json_valid(j->>'bar') THEN j->>'bar' ELSE NULL END
  -- Any other fields...
  from payload
}] foo bar

배열은 json_each()로 흡수할 수 있어요:

set json {[1, "two", 3.0, {"nested": true}, null]}
set myList [db eval {
  select value from json_each($json)
}]

# Emits: 1 two 3.0 {{"nested":true}} {}

객체 배열은 dict 객체 리스트로 분해할 수 있어요:

set json {[{"id":1, "v":"A"}, {"id":2, "v":"B"}]}
set myDictList [db eval {
  select json_object('id', value->>'id', 'v', value->>'v')
  from json_each($json)
}]

# Emits: {{"id":1,"v":"A"}} {{"id":2,"v":"B"}}

배열 객체 안의 객체 항목은 위 첫 번째 예시와 같은 접근 방식으로 분해할 수 있어요.

1.2. JSON 생성: DB 테이블을 JSON 객체로

여기에 설명된 접근 방식은 인메모리 db를 사용해 JSON 객체를 점진적으로 구축하고, 숫자와 문자열 같은 데이터 타입 사이의 경계를 자동으로 매끄럽게 해줘요.

#
# Initialize, if needed, the sqlite3 connection for the JR temp db and
# return its command name.
#
# JR = JSON Response (to an HTTP request)
#
proc jr-db {} {
  if {"" eq [info command ::__jrdb]} {
    sqlite3 ::__jrdb ":memory:"
    ::__jrdb eval {
      -- Holds a list of key/value pairs for a JSON response object
      create table jr(k TEXT UNIQUE ON CONFLICT REPLACE,v ANY);
    }
  }
  return ::__jrdb
}

#
# Remove all entries from the JR buffer.
#
proc jr-reset {args} {
  [jr-db] eval {delete from jr}
}

#
# Set one or more key/value pairs in the JR object.
#
proc jr-set {args} {
  set db [jr-db]
  foreach {k v} $args {
    $db eval {insert into jr(k,v) values($k,$v)}
  }
}

#
# Returns a JSON-format object representing the current JR state.
#
proc jr-get {} {
  [jr-db] onecolumn {
    select json_group_object(k,CASE WHEN json_valid(v,1)
                             THEN json(v) ELSE v END) from jr
  }
}

사용 예시:

jr-set message "This is a message."
jr-set aBool true isNull null anInt 37 aFloat 42.42
puts [jr-get]

결과:

{"message":"This is a message.","aBool":true,"isNull":null,"anInt":37,"aFloat":42.42}

1.3. JSON 생성: 점진적 구축

여기에 보여준 접근 방식은 몇 개의 루틴을 사용해 중첩된 객체나 배열을 점진적으로 구축하고, 정수, null, boolean처럼 보이는 값을 문자열이 아니라 그 자체로 생성할 수 있도록 타입 처리를 충실히 처리해요.

#
# Returns the command name of the shared SQLite3 instance used by the
# X-to-JSON APIs, initializing it on demand.
#
proc json-db {} {
  if {"" eq [info command ::__jsonDb]} {
    sqlite3 ::__jsonDb ":memory:"
  }
  return ::__jsonDb
}

#
# Given a dict-style object, this creates a JSON object representation.
# It does not handle nested objects but each value may itself be the result
# of its own call to this method, which has a similar effect.
#
proc json-obj {kvps} {
  set q "select json_object("
  set n 0
  set nn 1
  foreach {k v} $kvps {
    set $n $k
    set $nn $v
    if {$n} {append q ","}
    append q "\$$n, CASE WHEN json_valid(\$$nn) THEN json(\$$nn) ELSE \$$nn END"
    # This may look like an SQL injection opportunity, but it's
    # injecting prepared statement placeholders, not values.
    incr n 2
    incr nn 2
  }
  append q ")"
  [json-db] onecolumn $q
}

#
# Returns a JSON-format array of the given arguments. See json-object
# for notes about nesting.
#
proc json-array {args} {
  set n 0
  set q "select json_array("
  foreach a $args {
    set $n $a
    if {$n} {append q ","}
    append q "CASE WHEN json_valid(\$$n) THEN json(\$$n) ELSE \$$n END"
    incr n
  }
  append q ")"
  [json-db] onecolumn $q
}

사용 예시:

proc json-pretty {arg {space "  "}} {
  [json-db] onecolumn {select json_pretty($arg,$space)}
}
set o {
  hi world
  number 17.3
  truth true
  lies false
  nil null
  nada "null"
  notNull "Null"
}
lappend o list [json-array hello there [json-obj {nested true}]]
lappend o obj [json-obj {name "Nested object"}]
puts [json-pretty [json-obj $o]]

결과:

{
  "hi": "world",
  "number": 17.3,
  "truth": true,
  "lies": false,
  "nil": null,
  "nada": null,
  "notNull": "Null",
  "list": [
    "hello",
    "there",
    {
      "nested": true
    }
  ],
  "obj": {
    "name": "Nested object"
  }
}

더 알아보기 (Learn more)