Git Product home page Git Product logo

rl_json's Introduction

RL_JSON

This package adds a command [json] to the interpreter, and defines a new Tcl_Obj type to store the parsed JSON document. The [json] command directly manipulates values whose string representation is valid JSON, in a similar way to how the [dict] command directly manipulates values whose string representation is a valid dictionary. It is similar to [dict] in performance.

Also provided is a command [json template] which generates JSON documents by interpolating values into a template from a supplied dictionary or variables in the current call frame. The templates are valid JSON documents containing string values which match the regex "^~[SNBJTL]:.+$". The second character determines what the resulting type of the substituted value will be:

  • S: A string.
  • N: A number.
  • B: A boolean.
  • J: A JSON fragment.
  • T: A JSON template (substitutions are performed on the inserted fragment).
  • L: A literal - the resulting string is simply everything from the forth character onwards (this allows literal strings to be included in the template that would otherwise be interpreted as the substitutions above).

None of the first three characters for a template may be escaped.

The value inserted is determined by the characters following the substitution type prefix. When interpolating values from a dictionary they name keys in the dictionary which hold the values to interpolate. When interpolating from variables in the current scope, they name scalar or array variables which hold the values to interpolate. In either case if the named key or variable doesn't exist, a JSON null is interpolated in its place.

Quick Reference

  • [json get ?-default defaultValue? json_val ?key ...?] - Extract the value of a portion of the json_val, returns the closest native Tcl type (other than JSON) for the extracted portion.
  • [json extract ?-default defaultValue? json_val ?key ...?] - Extract the value of a portion of the json_val, returns the JSON fragment.
  • [json exists json_val ?key ...?] - Tests whether the supplied key path resolve to something that exists in json_val
  • [json set json_variable_name ?key ...? value] - Updates the JSON value stored in the variable json_variable_name, replacing the value referenced by key ... with the JSON value value.
  • [json unset json_variable_name ?key ...?] - Updates the JSON value stored in the variable json_variable_name, removing the value referenced by key ...
  • [json normalize json_val] - Return a "normalized" version of the input json_val - all optional whitespace trimmed.
  • [json template json_val ?dictionary?] - Return a JSON value by interpolating the values from dictionary into the template, or from variables in the current scope if dictionary is not supplied, in the manner described above.
  • [json string value] - Return a JSON string with the value value.
  • [json number value] - Return a JSON number with the value value.
  • [json boolean value] - Return a JSON boolean with the value value. Any of the forms accepted by Tcl_GetBooleanFromObj are accepted and normalized.
  • [json object ?key value ?key value ...??] - Return a JSON object with the keys and values specified. value is a list of two elements, the first being the type {string, number, boolean, null, object, array, json}, and the second being the value.
  • [json object packed_value] - An alternate syntax that takes the list of keys and values as a single arg instead of a list of args, but is otherwise the same.
  • [json array ?elem ...?] - Return a JSON array containing each of the elements given. elem is a list of two elements, the first being the type {string, number, boolean, null, object, array, json}, and the second being the value.
  • [json foreach varlist1 json_val1 ?varlist2 json_val2 ...? script] - Evaluate script in a loop in a similar way to the [foreach] command. In each iteration, the values stored in the iterator variables in varlist are the JSON fragments from json_val. Supports iterating over JSON arrays and JSON objects. In the JSON object case, varlist must be a two element list, with the first specifiying the variable to hold the key and the second the value. In the JSON array case, the rules are the same as the [foreach] command.
  • [json lmap varlist1 json_val1 ?varlist2 json_val2 ...? script] - As for [json foreach], except that it is collecting - the result from each evaluation of script is added to a list and returned as the result of the [json lmap] command. If the script results in a TCL_CONTINUE code, that iteration is skipped and no element is added to the result list. If it results in TCL_BREAK the iterations are stopped and the results accumulated so far are returned.
  • [json amap varlist1 json_val1 ?varlist2 json_val2 ...? script] - As for [json lmap], but the result is a JSON array rather than a list. If the result of each iteration is a JSON value it is added to the array as-is, otherwise it is converted to a JSON string.
  • [json omap varlist1 json_val1 ?varlist2 json_val2 ...? script] - As for [json lmap], but the result is a JSON object rather than a list. The result of each iteration must be a dictionary (or a list of 2n elements, including n = 0). Tcl_ObjType snooping is done to ensure that the iteration over the result is efficient for both dict and list cases. Each entry in the dictionary will be added to the result object. If the value for each key in the iteration result is a JSON value it is added to the array as-is, otherwise it is converted to a JSON string.
  • [json isnull json_val ?key ...?] - Return a boolean indicating whether the named JSON value is null.
  • [json type json_val ?key ...?] - Return the type of the named JSON value, one of "object", "array", "string", "number", "boolean" or "null".
  • [json length json_val ?key ...?] - Return the length of the of the named JSON array, number of entries in the named JSON object, or number of characters in the named JSON string. Other JSON value types are not supported.
  • [json keys json_val ?key ...?] - Return the keys in the of the named JSON object, found by following the path of keys.
  • [json pretty ?-indent indent? json_val ?key ...?] - Returns a pretty-printed string representation of json_val. Useful for debugging or inspecting the structure of JSON data.
  • [json decode bytes ?encoding?] - Decode the binary bytes into a character string according to the JSON standards. The optional encoding arg can be one of utf-8, utf-16le, utf-16be, utf-32le, utf-32be. The encoding is guessed from the BOM (byte order mark) if one is present and encoding isn't specified.
  • [json valid ?-extensions extensionlist? ?-details detailsvar? json_val] - Return true if json_val conforms to the JSON grammar with the extensions in extensionlist. Currently only one extension is supported: comments, and is the default. To reject comments, use -extensions {}. If -details detailsvar is supplied and the validation fails, the variable detailsvar is set to a dictionary with the keys errmsg, doc and char_ofs. errmsg contains the reason for the failure, doc contains the failing json value, and char_ofs is the character index into doc of the first invalid character.

Paths

The commands [json get], [json extract], [json set], [json unset] and [json exists] accept a path specification that names some subset of the supplied json_val. The rules are similar to the equivalent concept in the [dict] command, except that the paths used by [json] allow indexing into JSON arrays by the integer key (or a string matching the regex "^end(-[0-9]+)?$"). If a path to [json set] includes a key within an object that doesn't exist, it and all later elements of the path are created as nested keys into (new) objects. If a path element into an array is outside the current bounds of the array, it resolves to a JSON null (for [json get], [json extract], [json exists]), or appends or prepends null elements to resolve the path (for [json set], or does nothing ([json unset]).

json get {
    {
        "foo": [
            { "name": "first" },
            { "name": "second" },
            { "name": "third" }
        }
    }
} foo end-1 name

Returns "second"

Properly Interpreting JSON from Other Systems

Rl_json operates on characters, not bytes, and so considerations of encoding are strictly out of scope. However, interoperating with other systems properly in a way that conforms to the standards is a bit tricky, and requires support for encodings Tcl currently doesn't natively support, like utf-32be. To ease this burden and take care of things like replacing broken encoding sequences, the [json decode] subcommand is provided. Using it in an application would look something like:

proc readjson file {
    set h [open $file rb]    ;# Note that the file is opened in binary mode
    try {
        json decode [read $h]
    } finally {
        close $h
    }
}

If the encoding is known via some out-of-band channel (like headers in an HTTP response), it can be supplied to override the BOM-based detection. The supported encodings are those listed in the JSON standards: utf-8 (the default), utf-16le, utf-16be, utf-32le and utf-32be.

Examples

Creating a document from a template

Produce a JSON value from a template:

json template {
    {
        "thing1": "~S:val1",
        "thing2": ["a", "~N:val2", "~S:val2", "~B:val2", "~S:val3", "~L:~S:val1"],
        "subdoc1": "~J:subdoc",
        "subdoc2": "~T:subdoc"
    }
} {
    val1   hello
    val2   1e6
    subdoc {
        { "thing3": "~S:val1" }
    }
}

Result:

{"thing1":"hello","thing2":["a",1000000.0,"1e6",true,null,"~S:val1"],"subdoc1":{"thing3":"~S:val1"},"subdoc2":{"thing3":"hello"}}

Construct a JSON array from a SQL result set

# Given:
# sqlite> select * from languages;
# 'Tcl',1,'http://core.tcl-lang.org/'
# 'Node.js',1,'https://nodejs.org/'
# 'Python',1,'https://www.python.org/'
# 'INTERCAL',0,'http://www.catb.org/~esr/intercal/'
# 'Unlambda',0,NULL

set langs {[]}
sqlite3 db languages.sqlite3
db eval {
    select
        rowid,
        name,
        active,
        url
    from
        languages
} {
    if {$url eq ""} {unset url}

    json set langs end+1 [json template {
        {
            "id":       "~N:rowid",
            "name":     "~S:name",
            "details": {
                "active":   "~B:active",  // Template values can be nested anywhere
                "url":      "~S:url"      /* Both types of comments are
                                             allowed but stripped at parse-time */
            }
        }
    }]
}

puts [json pretty $langs]

Result:

[
    {
        "id":      1,
        "name":    "Tcl",
        "details": {
            "active": true,
            "url":    "http://core.tcl-lang.org/"
        }
    },
    {
        "id":      2,
        "name":    "Node.js",
        "details": {
            "active": true,
            "url":    "https://nodejs.org/"
        }
    },
    {
        "id":      3,
        "name":    "Python",
        "details": {
            "active": true,
            "url":    "https://www.python.org/"
        }
    },
    {
        "id":      4,
        "name":    "INTERCAL",
        "details": {
            "active": false,
            "url":    "http://www.catb.org/~esr/intercal/"
        }
    },
    {
        "id":      5,
        "name":    "Unlambda",
        "details": {
            "active": false,
            "url":    null
        }
    }
]

Performance

Good performance was a requirement for rl_json, because it is used to handle large volumes of data flowing to and from various JSON based REST apis. It's generally the fastest option for working with JSON values in Tcl from the options I've tried, with the next closest being yajltcl. These benchmarks report the median times in microseconds, and produce quite stable results between runs. Benchmarking was done on a MacBook Air running Ubuntu 14.04 64bit, Tcl 8.6.3 built with -O3 optimization turned on, and using an Intel i5 3427U CPU.

Parsing

This benchmark compares the relative performance of extracting the field containing the string "obj" from the JSON doc:

{
	"foo": "bar",
	"baz": ["str", 123, 123.4, true, false, null, {"inner": "obj"}]
}

The compared methods are:

Name Notes Code
old_json_parse Pure Tcl parser dict get [lindex [dict get [json_old parse [string trim $json]] baz] end] inner
rl_json_parse dict get [lindex [dict get [json parse [string trim $json]] baz] end] inner
rl_json_get Using the built-in accessor method json get [string trim $json] baz end inner
yajltcl dict get [lindex [dict get [yajl::json2dict [string trim $json]] baz] end] inner
rl_json_get_native json get $json baz end inner

The use of [string trim $json] is to defeat the caching of the parsed representation, forcing it to reparse the string each time since we're measuring the parse performance here. The exception is the rl_json_get_native test which demonstrates the performance of the cached case.

-- parse-1.1: "Parse a small JSON doc and extract a field" --------------------
                   | This run
    old_json_parse |  241.595
     rl_json_parse |    5.540
       rl_json_get |    4.950
           yajltcl |    8.800
rl_json_get_native |    0.800

Validating

If the requirement is to validate a JSON value, the [json valid] command is a light-weight version of the parsing engine that skips allocating values from the document and only returns whether the parsing succeeded or failed, and optionally a description of the failure. It takes about a third of the time to validate a document as parsing it, so the performance win is substantial. On a relatively modern CPU validation takes about 11 cycles per byte, or around 200MB of JSON per second on a 2.3 GHz Intel i7.

Generating

This benchmark compares the relative performance of various ways of dynamically generating a JSON document. Although all the methods produce the same string, only the "template" and "template_dict" variants handle nulls in the general case - the others manually test for null only for the one field that is known to be null, so the performance of these variants would be worse in a real-world scenario where all fields would need to be tested for null.

The JSON doc generated in each case is the one produced by the following JSON template (where a(not_defined) does not exist and results in a null value in the produced document):

{
	"foo": "~S:bar",
	"baz": [
		"~S:a(x)",
		"~N:a(y)",
		123.4,
		"~B:a(on)",
		"~B:a(off)",
		"~S:a(not_defined)",
		"~L:~S:not a subst",
		"~T:a(subdoc)",
		"~T:a(subdoc2)"
	]
}

The produced JSON doc is:

{"foo":"Bar","baz":["str\"foo\nbar",123,123.4,true,false,null,"~S:not a subst",{"inner":"Bar"},{"inner2":"Bar"}]}

The code for these variants are too long to include in this table, refer to bench/new.bench for the details.

Name Notes
old_json_fmt Pure Tcl implementation, builds JSON from type-annotated Tcl values
rl_json_new rl_json's [json new], API compatible with the pure Tcl version used in old_json_fmt
template rl_json's [json template]
yajltcl yajltcl's type-annotated Tcl value approach
template_dict As for template, but using a dict containing the values to substitute
yajltcl_dict As for yajltcl, but extracting the values from the same dict used by template_dict
-- new-1.1: "Various ways of dynamically assembling a JSON doc" ---------------
                 | This run
    old_json_fmt |   49.450
     rl_json_new |   10.240
        template |    4.520
         yajltcl |    7.700
   template_dict |    2.500
    yajltcl_dict |    7.530

Deprecations

Version 0.10.0 deprecates various subcommands and features, which will be removed in a near future version:

  • [json get_type json_val ?key ...?] - Removed
    • lassign [json get_type json_val ?key ...?] val type -> set val [json get json_val ?key ...?]; set type [json type json_val ?key ...?]
  • [json parse json_val] - A deprecated synonym for [json get json_val].
  • [json fmt type value] - A deprecated synonym for [json new type value], which is itself deprecated, see below.
  • [json new type value] - Use direct subcommands of [json]:
    • [json new string value] -> [json string value]
    • [json new number value] -> [json number value]
    • [json new boolean value] -> [json boolean value]
    • [json new true] -> true
    • [json new false] -> false
    • [json new null] -> null
    • [json new json value] -> value
    • [json new object ...] -> [json object ...] (but consider [json template])
    • [json new array ...] -> [json array ...] (but consider [json template])
  • modifiers at the end of a path - Modifiers like [json get json_val foo ?type] are deprecated. Replacements are:
    • ?type - use [json type json_val ?key ...?]
    • ?length - use [json length json_val ?key ...?]
    • ?size - use [json length json_val ?key ...?]
    • ?keys - use [json keys json_val ?key ...?]

Under the Hood

Older versions used the yajl c library to parse the JSON string and properly quote generated strings when serializing JSON values, but currently a custom built parser and string quoter is used, removing the libyajl dependency. JSON values are parsed to an internal format using Tcl_Objs and stored as the internal representation for a new type of Tcl_Obj. Subsequent manipulation of that value use the internal representation directly.

License

Copyright 2015-2023 Ruby Lane. Licensed under the same terms as the Tcl core.

rl_json's People

Contributors

apnadkarni avatar cyanogilvie avatar dkfellows avatar ecky-l avatar medranocalvo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rl_json's Issues

Create releases ?

Currently there are no releases of this software... is it at a point where it is stable to use ? A release would be helpful for referring to where to download a specific version of this package.

Wrong dispatch for unistd.h in generic/rl_jsonInt.h

The correct disp[atch for the presence of unistd.h is:

#include <math.h>
#include <stddef.h>
#include <stdint.h>

  • #if defined(HAVE_UNISTD_H)
    #include <unistd.h>
  • #endif
    #include <tclTomMath.h>
    #include "tip445.h"

Tcl_NREvalObj Error

Hi

Is there a minimum requirement of TCL8.6 for rl_json? I'm testing it in TCL8.5 and when I issue package require rl_json, I'm getting /lib/rl_json0.9.11/librl_json0.9.11.so: undefined symbol: Tcl_NREvalObj

Thanks

Steve

Configure/Compile errors for 0.15.1 under macos

Error when trying to configure/compile on macos (here Sonoma 14.4):

autoconf
configure.ac:246: warning: AC_C_BIGENDIAN should be used with AC_CONFIG_HEADERS

Compilation fails:

...
-c `echo /opt/projects/prs/g480/src/rl_json-0.15.1/generic/parser.c` -o parser.o
In file included from /opt/projects/prs/g480/src/rl_json-0.15.1/generic/parser.c:1:
/opt/projects/prs/g480/src/rl_json-0.15.1/generic/rl_jsonInt.h:16:10: fatal error: 'endian.h' file not found
#include <endian.h>
         ^~~~~~~~~~
1 error generated.

The file is not present. I tried to use <machine/endian.h>, but this fails as well with all bexxxx-functions missing:

...
-c `echo /opt/projects/prs/g480/src/rl_json-0.15.1/generic/cbor.c` -o cbor.o
/opt/projects/prs/g480/src/rl_json-0.15.1/generic/cbor.c:46:18: error: call to undeclared function 'be32toh'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
        uint32_t        uval = be32toh(*(uint32_t*)p);
...

Docs missing [json pretty]

Awesome command to have - dont know how i lived without it... would be nice to expose in docs for others!

rl_json crashes on macOS

I could easily compile and load rl_json (using wish 8.7a0), and also the code from the README works:

json get {
    {
        "foo": {
            "?size": "quite big"
        }
    }
} foo ??size

However, doing similar code with a larger json file crashed wish:

package require rl_json
namespace import rl_json::*
set fh [open neat-1.3.json r]
set md [read $fh]
close $fh
json get $md unMeta

This is the crash report from macOS:

Process:               Wish [11714]
Path:                  /usr/local/bin/wishtrunk
Identifier:            Wish
Version:               8.7a0 (8.7a0)
Code Type:             X86 (Native)
Parent Process:        bash [3370]
Responsible:           Wish [11714]
User ID:               501

Date/Time:             2017-05-31 10:07:01.522 +0200
OS Version:            Mac OS X 10.11.6 (15G1510)
Report Version:        11
Anonymous UUID:        0AAB9205-1216-8B3D-C187-8ED44B327C1A

Sleep/Wake UUID:       7CBAAECB-5985-4290-A8C8-8667B200A306

Time Awake Since Boot: 84000 seconds
Time Since Wake:       9500 seconds

System Integrity Protection: enabled

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY

Application Specific Information:
[11714] stack overflow

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib        	0x9cf32572 __pthread_kill + 10
1   libsystem_pthread.dylib       	0x9d980654 pthread_kill + 101
2   libsystem_c.dylib             	0x9807cd04 __abort + 187
3   libsystem_c.dylib             	0x9807d56f __stack_chk_fail + 236
4   librl_json0.9.8b1.dylib       	0x01ee36ce new_stringobj_dedup + 518
5   librl_json0.9.8b1.dylib       	0x01ee2ab2 value_type + 971
6   librl_json0.9.8b1.dylib       	0x01ee3138 set_from_any + 782
7   librl_json0.9.8b1.dylib       	0x01ee68b8 resolve_path + 48
8   librl_json0.9.8b1.dylib       	0x01ee44da jsonNRObjCmd + 1932
9   Tcl                           	0x0013688b 0x121000 + 88203
10  Tcl                           	0x00133a73 TclNRRunCallbacks + 70
11  Tcl                           	0x00134aae 0x121000 + 80558
12  Tcl                           	0x001d00aa Tcl_FSEvalFileEx + 774
13  Tk                            	0x0002a44d Tk_MainEx + 1603
14  wishtrunk                     	0x00004438 0x1000 + 13368
15  wishtrunk                     	0x00004405 0x1000 + 13317

Thread 1:: Dispatch queue: com.apple.libdispatch-manager
0   libsystem_kernel.dylib        	0x9cf337fa kevent_qos + 10
1   libdispatch.dylib             	0x900177ea _dispatch_mgr_invoke + 234
2   libdispatch.dylib             	0x900173be _dispatch_mgr_thread + 52

Thread 2:
0   libsystem_kernel.dylib        	0x9cf32d5e __workq_kernreturn + 10
1   libsystem_pthread.dylib       	0x9d97d34b _pthread_wqthread + 1289
2   libsystem_pthread.dylib       	0x9d97af56 start_wqthread + 34

Thread 3:
0   libsystem_kernel.dylib        	0x9cf32d5e __workq_kernreturn + 10
1   libsystem_pthread.dylib       	0x9d97d34b _pthread_wqthread + 1289
2   libsystem_pthread.dylib       	0x9d97af56 start_wqthread + 34

Thread 4:
0   libsystem_kernel.dylib        	0x9cf32d5e __workq_kernreturn + 10
1   libsystem_pthread.dylib       	0x9d97d34b _pthread_wqthread + 1289
2   libsystem_pthread.dylib       	0x9d97af56 start_wqthread + 34

Thread 5:
0   libsystem_kernel.dylib        	0x9cf32d5e __workq_kernreturn + 10
1   libsystem_pthread.dylib       	0x9d97d34b _pthread_wqthread + 1289
2   libsystem_pthread.dylib       	0x9d97af56 start_wqthread + 34

Thread 6:
0   libsystem_kernel.dylib        	0x9cf32726 __select + 10
1   Tcl                           	0x00227220 0x121000 + 1073696
2   libsystem_pthread.dylib       	0x9d97d780 _pthread_body + 138
3   libsystem_pthread.dylib       	0x9d97d6f6 _pthread_start + 155
4   libsystem_pthread.dylib       	0x9d97af7a thread_start + 34

Thread 0 crashed with X86 Thread State (32-bit):
  eax: 0x00000000  ebx: 0x66666667  ecx: 0xbffff16c  edx: 0x00000000
  edi: 0xa436d000  esi: 0x00000006  ebp: 0xbffff188  esp: 0xbffff16c
   ss: 0x00000023  efl: 0x00000206  eip: 0x9cf32572   cs: 0x0000000b
   ds: 0x00000023   es: 0x00000023   fs: 0x00000000   gs: 0x0000000f
  cr2: 0xa375da10
  
Logical CPU:     0
Error Code:      0x00080148
Trap Number:     132

Configure missing

It seems like configure is missing

It could just be me, but all instructions that I've found refer to running configure first. e.g., ./configure && make test && sudo make install. I'm not sure how to proceed.

Aside: It would be handy for new users if there was a short paragraph included in the readme on installing rl_json.

Create a 0.15.2 release?

Thanks so much for your work on rl_json! I tried to use rl_json v0.15.1 with Tcl 8.6 and ran into the following linker error:

undefined symbol: Tcl_GetBytesFromObj

I noticed that the very next commit after the v0.15.1 tag, 520d984, is a commit adding a polyfill that fixes this very issue for me. Would it be possible to create a new v0.15.2 release that includes this commit?

Request: Execute Commands in Templates

I have run into a few situations where it would be very helpful to allow executing of a command rather than substitution of a variable when running a template (with the ability to use variables if possible. I can of course set the variables before running the template, but in situations where I use the template from multiple procedures and need to update a value it definitely will be greatly beneficial to have.

I was thinking if there is a worry about substituting the commands directly that perhaps appending C could mean that it should also run any commands in the string? I could of course create all those situations where I just have a variable to directly reference but there are often times that ends up running into some messy situations, especially since many times i want to reference something that is not stored as a directly accessible variable.

Just a thought, obviously easy enough to work around it - just feel it kind of fits into the concept.

{
    "authToken": "~SC:[authToken]",
    "localTimestamp": "~NC:[clock milliseconds]",
    "payload": "~TC:[dict get $::JSON::Templates $request]"
 }

Obviously this also opens some other interesting capabilities like generating templates through return values of commands, etc.

Doesn't compile against 8.6.12-13

rl_json-0.11.5# make
gcc -DPACKAGE_NAME="rl_json" -DPACKAGE_TARNAME="rl_json" -DPACKAGE_VERSION="0.11.5" -DPACKAGE_STRING="rl_json\ 0.11.5" -DPACKAGE_BUGREPORT="" -DPACKAGE_URL="" -DBUILD_rl_json=/**/ -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DENSEMBLE=0 -DDEDUP=1 -DUSE_THREAD_ALLOC=1 -D_REENTRANT=1 -D_THREAD_SAFE=1 -DTCL_THREADS=1 -DUSE_TCL_STUBS=1 -DUSE_TCLOO_STUBS=1 -DMODULE_SCOPE=extern\ attribute((visibility("hidden"))) -DHAVE_HIDDEN=1 -DHAVE_CAST_TO_UNION=1 -DHAVE_STDBOOL_H=1 -D_LARGEFILE64_SOURCE=1 -DTCL_WIDE_INT_IS_LONG=1 -DTIP445_SHIM=1 -DHAVE_FFSLL=1 -DHAVE___BUILTIN_FFSLL=1 -DTCL_CFG_OPTIMIZED=1 -DTCL_MAJOR_VERSION=8 -I"/usr/local/tcl86/include" -pipe -O2 -fomit-frame-pointer -DNDEBUG -Wall -fPIC -c echo ./generic/parser.c -o parser.o
In file included from ./generic/rl_jsonInt.h:16,
from ./generic/parser.c:1:
./generic/tip445.h: In function ‘Tcl_FreeInternalRep’:
./generic/tip445.h:38:36: error: ‘Tcl_ObjType’ has no member named ‘freeInternalRepProc’; did you mean ‘freeIntRepProc’?
38 | if (obj->typePtr && obj->typePtr->freeInternalRepProc)
| ^~~~~~~~~~~~~~~~~~~
| freeIntRepProc
./generic/tip445.h:39:17: error: ‘Tcl_ObjType’ has no member named ‘freeInternalRepProc’; did you mean ‘freeIntRepProc’?
39 | obj->typePtr->freeInternalRepProc(obj);
| ^~~~~~~~~~~~~~~~~~~
| freeIntRepProc
At top level:
./generic/tip445.h:61:14: warning: ‘Tcl_InitStringRep’ defined but not used [-Wunused-function]
61 | static char* Tcl_InitStringRep(Tcl_Obj* objPtr, const char* bytes, unsigned numBytes)
| ^~~~~~~~~~~~~~~~~
make: *** [Makefile:327: parser.o] Error 1

json exists - empty values - validation

I am hoping I am not missing the obvious things here, loving this package! Just a few things so far that I have run into, I may have missed certain details that would be used to handle such things.

There is no way to validate a value may or may not be json that I have found without using a try/catch type option. For example:

set value {}
json exists $value key

This produces an error so there is really no way for me to run a command to properly handle the situation that I may not receive the json I am expecting, may have an empty value, or may unknowingly have a value that is not json (or perhaps already parsed from json). I know the json package does have a validate but IIRC there was a couple weaknesses of that and it was not a very efficient validator (I could be completely off-base with this).

If I am wrong with this then I will just start validating before every command - but I would still say mixing in the validation would make more sense in most cases.

Summary: Would like to see a json validate $value or json isjson $value and/or have json exists $json_value key return false in the case a value is not valid json.

Anyway, thank you for this package! It has really helped make a lot of our code much cleaner.

generating objects instead of arrays

Is there a way to include the id in the template?

$db foreach row "select id, from, to from table" {
  json set jt "[dict get $row id]" [json template {
    {   
      "from": "~S:from",
      "to": "~S:to",
    }
  } $row]
}

Best Alex

make install error

Compiling works fine, but "make install" returns the following error:

make: INSTALL_DATA_DIR@: command not found

Is there a better way to dynamicly create nested array

Hi,
I've been using rl_json a lot. Super handy TCL extension in data exchange.
Recently I found that I miss a function to create array dynamic. Something like 'lappend'.
Please check the code bellow:

package require rl_json
#generate list of JSON object to work with
set lines [list]
for {set i 0} {$i < 10} {incr i} {
	rl_json::json set line  tag  [rl_json::json new string marker$i]
	rl_json::json set line  data [rl_json::json new string "dummy data$i"]
	lappend lines $line
}
#puts $lines
#{{"tag":"marker0","data":"dummy data0"}} {{"tag":"marker1","data":"dummy data1"}} {{"tag":"marker2","data":"dummy data2"}} \
#{{"tag":"marker3","data":"dummy data3"}} {{"tag":"marker4","data":"dummy data4"}} {{"tag":"marker5","data":"dummy data5"}} \
#{{"tag":"marker6","data":"dummy data6"}} {{"tag":"marker7","data":"dummy data7"}} {{"tag":"marker8","data":"dummy data8"}} \
#{{"tag":"marker9","data":"dummy data9"}}

#Create main JSON object
set d_json [rl_json::json template {{
            "id": "~S:id",
            "tStamp": "~N:tStamp",
            "lines": [{
                "tag": "~S:tag",
                "data": "~S:data"
            }]
        }} [dict create \
            tStamp [clock milliseconds]\
            id "obj13"\
            tag "marker"\
            data "dummy data"]]
#{"id":"obj13","tStamp":1571222639778,"lines":[{"tag":"marker","data":"dummy data"}]}
puts [rl_json::json pretty $d_json]

#Append JSON objects (lines) to array in main JSON object (d_json)
foreach line $lines {
	::rl_json::json set d_json lines [string replace [::rl_json::json extract $d_json lines] end end ",$line\]"]
}
#10 lines - 120 microseconds per iteration
#100 lines - 7302 microseconds per iteration
#1000 lines - 944830 microseconds per iteration

#puts [rl_json::json pretty $d_json]

to dynamic add new object to the array I use such ugly construction:

::rl_json::json set d_json lines [string replace [::rl_json::json extract $d_json lines] end end ",$line\]"]

There is a lot of allocation which makes it very slow for 20+ objects in array (see the snippet above)

I was looking for something like lappend or append instead of set, something like this:

::rl_json::json append d_json lines $line

or

::rl_json::json append d_json lines [rl_json::json new json $line]

but I couldn't find.

Is there any better way to build array than take existing array and do use string operation on it and than reassign to JSON object?

Thanks.

Add JSON content validation

I'm starting using rl_json a lot and find it quiet useful for Tcl, as it allows typed string representation to Tcl easily and efficiently.
However, I have now a lot of code to validate content of JSON (array, object content, field type, ...).
Could an easy way to check / scan JSON be added?
Something similar to schema validation function in tDOM? Or based on JSON tempate?

Infinite Loop issues

Hey Cyan,
So I've been dealing with a really crazy bug I haven't been able to track down now for weeks. At some point I suddenly start getting non-stop infinite loop errors reported. It is always in a different place in my code and seems to be completely random.

I did notice most of the problems were coming from some sort of JSON-related proc. I believe I have narrowed this down to rl_json, specifically perhaps the [foreach] command. Tcl chatroom mentioned it is quite possibly a problem if it isn't NRE enabled and i just noticed in source you have that as a todo.

Anyways, going to try switching to just using json get and then looping it from there, but wanted to mention it in the meantime.

My guess is the API spits out some weird code that ends up causing the issue.

Compilation failure under gcc 14.1.1...

Hi all,

The code does not compile under gcc 14.1.1 (Fedora 40):

./generic/cbor.c: In function ?cbor_matches?:
./generic/cbor.c:713:113: ??????: passing argument 3 of ?tclStubsPtr->tcl_GetBytesFromObj? from incompatible pointer type [-Wincompatible-pointer-types]
  713 |                         const uint8_t*          pathval = (const uint8_t*)Tcl_GetBytesFromObj(interp, pathElem, &pathlen);
      |                                                                                                                 ^~~~~~~~
      |                                                                                                                 |
      |                                                                                                                 size_t * {aka long unsigned int *}
./generic/cbor.c:713:113: ????????: expected ?int *? but argument is of type ?size_t *? {aka ?long unsigned int *?}
./generic/cbor.c: In function ?CBOR_GetDataItemFromPath?:
./generic/cbor.c:968:54: ??????: passing argument 3 of ?tclStubsPtr->tcl_GetBytesFromObj? from incompatible pointer type [-Wincompatible-pointer-types]
  968 |         bytes = Tcl_GetBytesFromObj(interp, cborObj, &byteslen);
      |                                                      ^~~~~~~~~
      |                                                      |
      |                                                      size_t * {aka long unsigned int *}
./generic/cbor.c:968:54: ????????: expected ?int *? but argument is of type ?size_t *? {aka ?long unsigned int *?}

rl_json 0.11.1 fails to link under Windows 10 with cl when cmpiled in 32 bit mode

Here is a trace of the log:

cl -DPACKAGE_NAME="rl_json" -DPACKAGE_TARNAME="rl_json" -DPACKAGE_VERSION="0.11.1" -DPACKAGE_STRING="rl_json\ 0.11.1" -DPACKAGE_BUGREPORT="" -DPACKAGE_URL="" -DBUILD_rl_json=/**/
-DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DENSEMBLE=0 -DDEDUP=1 -DTCL_THREADS=1 -D
USE_TCL_STUBS=1 -DUSE_TCLOO_STUBS=1 -DMODULE_SCOPE=extern -DHAVE_STDBOOL_H=1 -DTIP445_SHIM=1 -I"P:/g480/gbuild/rel/wintel/include" -nologo -O2 -W2 -MD -DNDEBUG -c cygpath -m /p/g480/sr c/rl_json-0.11.1/generic/rl_json.c -o rl_json.obj
cl : Command line warning D9035 : option 'o' has been deprecated and will be removed in a future release
rl_json.c
cl -DPACKAGE_NAME="rl_json" -DPACKAGE_TARNAME="rl_json" -DPACKAGE_VERSION="0.11.1" -DPACKAGE_STRING="rl_json\ 0.11.1" -DPACKAGE_BUGREPORT="" -DPACKAGE_URL="" -DBUILD_rl_json=/**/
-DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DENSEMBLE=0 -DDEDUP=1 -DTCL_THREADS=1 -D
USE_TCL_STUBS=1 -DUSE_TCLOO_STUBS=1 -DMODULE_SCOPE=extern -DHAVE_STDBOOL_H=1 -DTIP445_SHIM=1 -I"P:/g480/gbuild/rel/wintel/include" -nologo -O2 -W2 -MD -DNDEBUG -c cygpath -m /p/g480/sr c/rl_json-0.11.1/generic/dedup.c -o dedup.obj
cl : Command line warning D9035 : option 'o' has been deprecated and will be removed in a future release
dedup.c
rm -f rl_json0111.dll
link -dll -nodefaultlib:libucrt.lib -nologo ucrt.lib "P:/g480/gbuild/rel/wintel/lib/tclstub86.lib" -release -out:rl_json0111.dll parser.obj rl_json.obj json_types.obj dedup.obj api.obj rl_js
onStubInit.obj names.obj ; if test -f rl_json0111.dll.manifest ; then mt.exe -nologo -manifest rl_json0111.dll.manifest -outputresource:rl_json0111.dll;2 ; fi
Creating library rl_json0111.lib and object rl_json0111.exp
rl_json.obj : error LNK2019: unresolved external symbol _mkstemp referenced in function _checkmem
dedup.obj : error LNK2019: unresolved external symbol _ffs referenced in function _first_free
rl_json0111.dll : fatal error LNK1120: 2 unresolved externals
: rl_json0111.dll

Replace libyajl with custom json parser

Building this package is made much more difficult because it requires a newer version of libyajl than is carried by most distributions, and to get around certain parsing bugs it is necessary to use a forked version with fixes. Pull requests on the yajl project were raised months ago to contribute the fixes upstream but they haven't been picked up, and building using a non-system libyajl is very messy, requiring overrides to PKG_CONFIG_LIBDIR and editing the paths in the installed header files.

To address these issues it would be best to spin an internal json parser and remove the libyajl dependency.

Start using tags for versions / releases

rkeene touched on this in a previous post requesting that you do something like this. It isn't a huge deal but I added rl_json to the kitcreator (https://kitcreator.rkeene.org/fossil/home) build system which downloads the source and compiles it into a tcl distribution. Right now with how it is setup we have to point to a specific commit

https://github.com/RubyLane/rl_json/archive/f4ad2a2755bedc9deb36cef245d85f593dc4c655.tar.gz

-- When using releases we can point to those instead. I believe that if you were to add the tag to each version change then we can do

https://github.com/RubyLane/rl_json/archive/0.9.6.tar.gz

(Although I am not positive on that since I dont often use github).

I know that can be done with branches. When I do tags I run:

git tag -a $::version -m "Version $::version build $::build"

and by the way here is the kitcreator script to build rl_json into the tclkit:

#! /usr/bin/env bash

# BuildCompatible: KitCreator

version='0.9.5'
url="https://github.com/RubyLane/rl_json/archive/f4ad2a2755bedc9deb36cef245d85f593dc4c655.tar.gz"
sha256='0c5625437edf5863724e190be1984d3e51e91c84d32c00a12c89da4436b772fd'
internalpkgname='rl_json'
tclpkg='rl_json'
tclpkgversion='0.9.5'

Patch for making the code work on windows

Hello,

I created a patch to make the code work on windows. The main part is the "BitScanForward()" function, in place of the unix only ffsll() function. And the VA_ARGS variable argument macro expansion, which was also not compilable with MSVC.
All the tests run through. Apart from the tests which check against funny unicode characters - but I think this is an encoding problem on windows. If the unicode characters are replaced by their \u... sequence in the test file, everything works and all tests are green.

In parser.h is a struct definition interp_cx, with a field interp_cx.freemap. It was defined as long long, with a remark that this is needed for ffsll(). I changed the definition to unsigned long, since that was more compatible with the Windows version of that function BitScanForward().. I found out that on linux it does also not have any consequences, for my test runs, compilation etc. it didn't matter whether its long long or unsigned long. Would it be an option to change that to unsigned long generally?

Apart from that, I think the patch needs some review and cleanup, but it would be good to incorporate it for the windows users :).

rl_json_el.zip

One more correction for rl_json (missed this one in last report)

Here is another small fix for 0.11.1 (initialization of struct in non-GNU compilation). Thanks for creating 0.11.1 including the fixes sent the other day.

*** ./generic/rl_json.c 2021-11-23 22:51:03.000000000 +0100
--- ../../rl_json-0.11.1/generic/rl_json.c 2021-11-24 10:00:21.940629641 +0100


*** 3009,3015 ****
{
struct interp_cx* l = (struct interp_cx*)cdata;
int i, valid, retval=TCL_OK;
! struct parse_error details = {};
Tcl_Obj* detailsvar = NULL;
enum extensions extensions = EXT_COMMENTS; // By default, use the default set of extensions
we accept
static const char options[] = {
--- 3009,3015 ----
{
struct interp_cx
l = (struct interp_cx*)cdata;
int i, valid, retval=TCL_OK;
! struct parse_error details = {0};
Tcl_Obj* detailsvar = NULL;
enum extensions extensions = EXT_COMMENTS; // By default, use the default set of extensions
we accept
static const char *options[] = {

How can I use the offset reported in an error?

When [usually inadvertently] trying to create invalid JSON, I may come across an error such as Illegal character at offset 48. What does the offset actually mean here? Whenever I see it, the [in this case] 48th character of the JSON object doesn't make sense, nor does the 48th character in any string of sufficient length. How do I decode this error?

Fails to compile with mingw...

Hi all,

I cannot compile rl_json with mingw under Fedora.
There is no file named "endian.h".

dnf whatprovides */endian.h |grep mingw
mingw32-boost-1.78.0-4.fc38.noarch : MinGW Windows Boost C++ library for the win32 target
Filename    : /usr/i686-w64-mingw32/sys-root/mingw/include/boost/predef/other/endian.h
mingw64-boost-1.78.0-4.fc38.noarch : MinGW Windows Boost C++ library for the win64 target
Filename    : /usr/x86_64-w64-mingw32/sys-root/mingw/include/boost/predef/other/endian.h

Issue with using dict with variables with json set

There seems to be a bug when trying to use dict with variables in json set:

Example

% package require rl_json
0.9.11

% set a {test {{"a":"b"}}}
% dict with a { rl_json::json get $test a }
b

% dict with a { rl_json::json set test a d }
Error parsing JSON value: Illegal character at offset 0

Assigning values to a json structur

Hi!

I'm testing rl_json right now. Parsing JSON strings and getting values works fine. But when I try to set values, I'm having some difficulties. Consider the following code:

package require rl_json
set jsonstr {
    {
        "foo": "Foo",
        "baz": "Baz"
    }
}

json set jsonstr bar Bar FooBar test {"x"}
puts [json normalize $jsonstr]

json set jsonstr bar Bar FooBar test2 {"y"}
puts [json normalize $jsonstr]

json set jsonstr bar Bar FooBar test3 {"z"}
puts [json normalize $jsonstr]

I'd expect the following end result:

{"foo":"Foo","baz":"Baz","bar":{"Bar":{"FooBar":{"test":"x","test2":"y","test3":"z"}}}}

Instead I get this result:

{"foo":"Foo","baz":"Baz","bar":{"Bar":{"FooBar":{"test":"x","test2":null,"bar":{"Bar":{"FooBar":{"test3":"y"}}},"test3":null}}}}

Another problem I have is changing a value, e.g:

package require rl_json

set jsonstr {
    {
        "foo": "Foo",
        "baz": "Baz"
    }
}

json set jsonstr foo {"Bar"}
puts [json normalize $jsonstr]

exits with the error message

missing value to go with key

The speed of the implementation is very impressive. If setting values would work, JSON would really feel like a native datatype of TCL.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.