tjson

TCL/C extension for parsing JSON

Contact: neophytos (at) gmail (dot) com

tjson git repo

Releases

  • v1.0.25 (2024-08-05 - added get_child_items cmd)
  • v1.0.24 (2024-07-30 - reverted CreateExitHandler change)
  • v1.0.20 (2024-07-18 - added is_null, is_string, is_bool, is_number, is_object, and is_array cmds + initialize stubs differently)
  • v1.0.17 (2024-07-06 - fixed issue with CreateItemFromSpec for BOOL values)
  • v1.0.16 (2024-06-30 - replaced int with Tcl_Size in a couple of places)
  • v1.0.15 (2024-06-29 - added has_object_item cmd)
  • v1.0.12 (2024-04-06 - bumped up version - fixed some version issues)
  • v1.0.11 (2024-03-10 - initial support for TCL 9)
  • v1.0.10 (2023-10-01 - trace var in create and parse commands)
  • v1.0.9 (2023-09-30 - windows support)
  • v1.0.8 (2023-09-28 - bug fix release)
  • v1.0.6 (2023-09-28 - macOS support / more flexible build/install)

Features

  • Simple transformations between TCL and JSON using a typed notation/spec.
  • Manipulate objects and arrays (add, replace, and delete items and elements).
  • Perform JSONPath queries
  • Serialize to JSON, pretty JSON, or a simple TCL structure i.e. string, list, and dict.
# Parse JSON string and return a simple TCL structure
# ::tjson::json_to_simple json_string

::tjson::json_to_simple {{"a": 1, "b": true, "c": [1, 2, 3], "d": {"d1":"a", "d2":"b"}}} true
=> a 1 b 1 c {1 2 3} d {d1 a d2 b} 

# Parse JSON string and return a typed TCL structure
# ::tjson::json_to_typed json_string

::tjson::json_to_typed {{"a": 1, "b": true, "c": [1, 2, 3], "d": {"d1":"a", "d2":"b"}}}
=> M {a {N 1} b {BOOL 1} c {L {{N 1} {N 2} {N 3}}} d {M {d1 {S a} d2 {S b}}}}

# Serialize a typed TCL structure to JSON.
# ::tjson::typed_to_json typed_spec

::tjson::typed_to_json {M {a {N 1} b {BOOL 1} c {L {{N 1} {N 2} {N 3}}} d {M {d1 {S a} d2 {S b}}}}}
=> {"a": 1, "b": true, "c": [1, 2, 3], "d": {"d1": "a", "d2": "b"}}

# Escape JSON string
# ::tjson::escape_json_string string

::tjson::escape_json_string "hello\nworld\n"
=> hello\"world\n

Here is an example of parsing and manipulating the JSON node:

package require tjson

set node_handle [::tjson::parse {
    { "store": {
       "book": [
         { "category": "reference", "author": "Nigel Rees",
           "title": "Sayings of the Century", "price": "8.95"  },
         { "category": "fiction", "author": "Evelyn Waugh",
           "title": "Sword of Honour", "price": "12.99" },
         { "category": "fiction", "author": "Herman Melville",
           "title": "Moby Dick", "isbn": "0-553-21311-3",
           "price": "8.99" },
         { "category": "fiction", "author": "J. R. R. Tolkien",
           "title": "The Lord of the Rings", "isbn": "0-395-19395-8",
           "price": "22.99" }
       ],
       "bicycle": { "color": "red", "price": "19.95" }
     }
   }}]

set store_handle [::tjson::get_object_item $node_handle store]
::tjson::add_item_to_object $store_handle car [list M \
    [list brand {S "Mercedes"} color {S blue} price {N 19876.57}]]

set book_handle [::tjson::get_object_item $store_handle book]
::tjson::add_item_to_array $book_handle [list M \
    [list \
        category {S "fiction"} \
        author {S "Fyodor Dostoevsky"} \
        title {S "Brothers Karamazov"} \
        isbn {S "0679410031"} \
        price {N "22.19"}]]

puts [::tjson::to_pretty_json $node_handle]
::tjson::destroy $node_handle

It is possible to perform JSONPath queries as follows:

set jsonpath {$.store..author}
set item_handles [::tjson::query $handle $jsonpath]
lmap x $item_handles {::tjson::to_simple $x}

Performance

Download this file: https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json

package require tjson
set fp [open "spec3.json"]
set data [read $fp]
close $fp
puts [time {::tjson::destroy [::tjson::parse $data]} 1000]

=> 27935.83 microseconds per iteration