| What | build JSON with tdom using createNodeCmd |
| Requires | tdom 0.9.2 |
| tags | tdom |
| reference | http://www.tdom.org/index.html/doc/trunk/doc/index.html |
| Updated | 03.07.2022 |
how to build a JSON like this:
{
"aNumber": 0.123,
"aNumberAsString": "0.123",
"aString": "this is a String",
"a key with spaces": "please take care on setting: dom setNameCheck 0",
"aArray": ["a","b","c"],
"aObject": {
"foo":"that",
"bar":"grill"
},
"aObjectArray": [
{
"foo": "objArray_01_foo",
"bar": "objArray_01_bar",
"array": ["five_01_a", "five_01_b", "five_01_c"]
},
{
"foo": "objArray_02_foo",
"bar": "objArray_02_bar",
"array": ["five_02_a", "five_02_b", "five_02_c"]
}
]
} #
# --- load tdom ---
#
package require tdom 0.9 ;# current version: 0.9.2 (May, 2022)
puts "tdom: [package versions tdom]"
#
# http://www.tdom.org/index.html/file?name=tests/domjson.test&ci=release
#
#
set doc [dom createDocumentNode root]
#
puts "asXML: [$root asXML -indent 4]"
puts "asJSON: [$root asJSON -indent 4]"
#
# -- aNumber --
#
set node_Number [$doc createElement "aNumber"]
$node_Number appendChild [$doc createTextNode 0.123]
$root appendChild $node_Number
#
# -- aNumberAsString --
#
set node_StringNum [$doc createElement "aNumberAsString"]
$node_StringNum appendChild [$doc createTextNode 0.123]
$root appendChild $node_StringNum
#
# -- aString --
#
set node_String [$doc createElement "aNumberString"]
$node_String appendChild [$doc createTextNode "0.123"]
$root appendChild $node_String
#
set node_String [$doc createElement "aString"]
$node_String appendChild [$doc createTextNode "this is a String"]
$root appendChild $node_String
#
# -- a key with spaces --
#
# ... you have to disable namecheck first
dom setNameCheck 0
set node_String [$doc createElement "a key with spaces"]
# [dom setNameCheck] == 1 ... brings this error
# ERROR: Invalid tag name 'a key with spaces'
# while executing
# "$doc createElement "a key with spaces""
# invoked from within
# "set node_String [$doc createElement "a key with spaces"]"
#
$node_String appendChild [$doc createTextNode "please take care on setting: dom setNameCheck 0"]
$root appendChild $node_String
#
# -- aArray --
#
set node_Array [$doc createElement "aArray"]
$node_Array appendChild [$doc createTextNode "a"]
$node_Array appendChild [$doc createTextNode "b"]
$node_Array appendChild [$doc createTextNode "c"]
$root appendChild $node_Array
#
#
# -- aObject --
#
set node_Object [$doc createElement "aObject"]
#
set node__Label [$doc createElement "foo"]
set node__Value [$doc createTextNode "that"]
$node_Object appendChild $node__Label
$node__Label appendChild $node__Value
#
set node__Label [$doc createElement "bar"]
set node__Value [$doc createTextNode "grill"]
$node_Object appendChild $node__Label
$node__Label appendChild $node__Value
#
$root appendChild $node_Object
#
#
# -- aObjectArray --
#
set node_ObjArray [$doc createElement "aObjectArray"]
#
set node__Obj_01 [$doc createElement "obj0001"]
#
set node__Label [$doc createElement "foo"]
set node__Value [$doc createTextNode "objArray_01_foo"]
$node__Label appendChild $node__Value
$node__Obj_01 appendChild $node__Label
set node__Label [$doc createElement "bar"]
set node__Value [$doc createTextNode "objArray_01_bar"]
$node__Label appendChild $node__Value
$node__Obj_01 appendChild $node__Label
#
set node__Array [$doc createElement "array"]
$node__Array appendChild [$doc createTextNode "five_01_a"]
$node__Array appendChild [$doc createTextNode "five_01_b"]
$node__Array appendChild [$doc createTextNode "five_01_c"]
#
$node__Obj_01 appendChild $node__Array
#
#
set node__Obj_02 [$doc createElement "obj0002"]
#
set node__Label [$doc createElement "foo"]
set node__Value [$doc createTextNode "objArray_02_foo"]
$node__Label appendChild $node__Value
$node__Obj_02 appendChild $node__Label
set node__Label [$doc createElement "bar"]
set node__Value [$doc createTextNode "objArray_02_bar"]
$node__Label appendChild $node__Value
$node__Obj_02 appendChild $node__Label
#
set node__Array [$doc createElement "array" ARRAY]
$node__Array appendChild [$doc createTextNode "five_02_a"]
$node__Array appendChild [$doc createTextNode "five_02_b"]
$node__Array appendChild [$doc createTextNode "five_02_c"]
#
$node__Obj_02 appendChild $node__Array
#
$node_ObjArray appendChild $node__Obj_01
$node_ObjArray appendChild $node__Obj_02
#
#
$root appendChild $node_ObjArray
#
# in the case of mixed OBJECT and ARRAYs you have to update the specific rendering
$node_ObjArray jsonType ARRAY
$node__Obj_01 jsonType OBJECT
$node__Obj_02 jsonType OBJECT
#
puts "== \$resultJSON ===="
puts [$doc asXML -indent 4]
puts "== \$resultJSON ===="
puts [$doc asJSON -indent 4]
#... output of this script:
tdom: 0.9.2
asXML:
asJSON: {}
== $resultJSON ====
<aNumber>0.123</aNumber>
<aNumberAsString>0.123</aNumberAsString>
<aNumberString>0.123</aNumberString>
<aString>this is a String</aString>
<a key with spaces>please take care on setting: dom setNameCheck 0</a key with spaces>
<aArray>abc</aArray>
<aObject>
<foo>that</foo>
<bar>grill</bar>
</aObject>
<aObjectArray>
<obj0001>
<foo>objArray_01_foo</foo>
<bar>objArray_01_bar</bar>
<array>five_01_afive_01_bfive_01_c</array>
</obj0001>
<obj0002>
<foo>objArray_02_foo</foo>
<bar>objArray_02_bar</bar>
<array>five_02_afive_02_bfive_02_c</array>
</obj0002>
</aObjectArray>
== $resultJSON ====
{
"aNumber": "0.123",
"aNumberAsString": "0.123",
"aNumberString": "0.123",
"aString": "this is a String",
"a key with spaces": "please take care on setting: dom setNameCheck 0",
"aArray": [
"a",
"b",
"c"
],
"aObject": {
"foo": "that",
"bar": "grill"
},
"aObjectArray": [
{
"foo": "objArray_01_foo",
"bar": "objArray_01_bar",
"array": [
"five_01_a",
"five_01_b",
"five_01_c"
]
},
{
"foo": "objArray_02_foo",
"bar": "objArray_02_bar",
"array": [
"five_02_a",
"five_02_b",
"five_02_c"
]
}
]
}this Result: "aNumber": "0.123" expected Result: "aNumber": 0.123
... the given approach takes a lot of lines to create this result
use
dom createNodeCmd
to improve our code
#
# --- load tdom ---
#
package require tdom 0.9 ;# current version: 0.9.2 (May, 2022)
puts "tdom: [package versions tdom]"
#
# http://www.tdom.org/index.html/file?name=tests/domjson.test&ci=release
#
#
#
puts "== 01 == \$resultJSON ===="
set doc [dom createDocumentNode root]
#
puts "asXML: [$root asXML -indent 4]"
puts "asJSON: [$root asJSON -indent 4]"
#
#
# our targetJSON includes nodes with predefined names:
# "aNumber" "aString" "a key with spaces" "foo" "bar"
#
# ... lets predefine dom-commands to create these nodes
#
dom createNodeCmd -jsonType NONE elementNode aNumber
dom createNodeCmd -jsonType NONE elementNode aNumberAsString
dom createNodeCmd -jsonType NONE elementNode aString
dom createNodeCmd -jsonType NONE elementNode "a key with spaces"
dom createNodeCmd -jsonType NONE elementNode foo
dom createNodeCmd -jsonType NONE elementNode bar
dom createNodeCmd -jsonType NONE elementNode array
dom createNodeCmd -jsonType ARRAY elementNode "aArray"
#
# ... json distinguishs between
# ... Arrays ["A","B","C"] and
# ... Objects {"a": "A", "b": "B", "c":"C"}
#
dom createNodeCmd -jsonType OBJECT elementNode "aObject"
dom createNodeCmd -jsonType ARRAY elementNode "aObjectArray"
#
# ... json distinguishs between
# ... Strings "0.123"
# ... Numbers 0.123
#
dom createNodeCmd -jsonType NUMBER textNode jsonNumber
dom createNodeCmd -jsonType STRING textNode jsonString
#
#
set resultJSON [dom createDocumentNode]
#
puts "== 02 == \$resultJSON ===="
puts [$resultJSON asJSON -indent 4]
#
$resultJSON appendFromScript {
aNumber {jsonNumber 0.123}
aNumberAsString {jsonString 0.123}
aString {jsonString "this is a string"}
"a key with spaces" {jsonString "It's possible."}
aArray {
jsonString a
jsonString b
jsonString c
}
aObject {
foo {jsonString "that"}
bar {jsonString "grill"}
}
aObjectArray {
aObject {
foo {jsonString "objArray_01_foo"}
bar {jsonString "objArray_01_bar"}
aArray {
jsonString "five_01_a"
jsonString "five_01_b"
jsonString "five_01_c"
}
}
aObject {
foo {jsonString "objArray_02_foo"}
bar {jsonString "objArray_02_bar"}
aArray {
jsonString "five_02_a"
jsonString "five_02_b"
jsonString "five_02_c"
}
}
}
}
#
#
puts "== 03 == \$resultJSON ===="
puts [$resultJSON asXML -indent 4]
puts "== 03 == \$resultJSON == final =="
puts [$resultJSON asJSON -indent 4]
#... output of this script:
tdom: 0.9.2
== 01 == $resultJSON ====
asXML:
asJSON: {}
== 02 == $resultJSON ====
{}
== 03 == $resultJSON ====
<aNumber>0.123</aNumber>
<aNumberAsString>0.123</aNumberAsString>
<aString>this is a string</aString>
<a key with spaces>It's possible.</a key with spaces>
<aArray>abc</aArray>
<aObject>
<foo>that</foo>
<bar>grill</bar>
</aObject>
<aObjectArray>
<aObject>
<foo>objArray_01_foo</foo>
<bar>objArray_01_bar</bar>
<aArray>five_01_afive_01_bfive_01_c</aArray>
</aObject>
<aObject>
<foo>objArray_02_foo</foo>
<bar>objArray_02_bar</bar>
<aArray>five_02_afive_02_bfive_02_c</aArray>
</aObject>
</aObjectArray>
== 03 == $resultJSON == final ==
{
"aNumber": 0.123,
"aNumberAsString": "0.123",
"aString": "this is a string",
"a key with spaces": "It's possible.",
"aArray": [
"a",
"b",
"c"
],
"aObject": {
"foo": "that",
"bar": "grill"
},
"aObjectArray": [
{
"foo": "objArray_01_foo",
"bar": "objArray_01_bar",
"aArray": [
"five_01_a",
"five_01_b",
"five_01_c"
]
},
{
"foo": "objArray_02_foo",
"bar": "objArray_02_bar",
"aArray": [
"five_02_a",
"five_02_b",
"five_02_c"
]
}
]
}... your comments
rattleCAD 2022-07-03 ... you may improve this by replace:
dom createNodeCmd -jsonType NONE elementNode aNumber dom createNodeCmd -jsonType NONE elementNode aNumberAsString dom createNodeCmd -jsonType NONE elementNode aString dom createNodeCmd -jsonType NONE elementNode "a key with spaces" dom createNodeCmd -jsonType NONE elementNode foo dom createNodeCmd -jsonType NONE elementNode bar dom createNodeCmd -jsonType NONE elementNode array dom createNodeCmd -jsonType ARRAY elementNode "aArray" dom createNodeCmd -jsonType OBJECT elementNode "aObject" dom createNodeCmd -jsonType ARRAY elementNode "aObjectArray" dom createNodeCmd -jsonType NUMBER textNode jsonNumber dom createNodeCmd -jsonType STRING textNode jsonString
by
foreach {nodeName nodeType jsonType} {
aNumber elementNode NONE
aNumberAsString elementNode NONE
aString elementNode NONE
"a key with spaces" elementNode NONE
foo elementNode NONE
bar elementNode NONE
array elementNode NONE
"aArray" elementNode ARRAY
"aObject" elementNode OBJECT
"aObjectArray" elementNode ARRAY
jsonNumber textNode NUMBER
jsonString textNode STRING
} {
# dom createNodeCmd -jsonType STRING textNode jsonString
# puts " -> dom createNodeCmd -jsonType [format {%-8s %-12s %s} $jsonType $nodeType $nodeName]"
dom createNodeCmd -jsonType $jsonType $nodeType $nodeName
} sbron 2022-08-18: I wasn't very satisfied with the need to hardcode the createNodeCmd commands. So I came up with a more general approach, using the namespace unknown functionality. While making that, it occurred to me that this also allowed for a nice way to pass in variables to be substituted:
package require tdom 0.9.3-
namespace eval json {
namespace ensemble create -subcommands build
namespace eval eval {
namespace unknown [namespace parent]::unknown
dom createNodeCmd -jsonType OBJECT elementNode object
dom createNodeCmd -jsonType ARRAY elementNode array
dom createNodeCmd -jsonType NUMBER textNode number
dom createNodeCmd -jsonType STRING textNode string
dom createNodeCmd -jsonType TRUE textNode true
dom createNodeCmd -jsonType FALSE textNode false
dom createNodeCmd -jsonType NULL textNode null
proc boolean {arg} {
if {$arg} {
tailcall true true
} else {
tailcall false false
}
}
}
proc unknown {cmd args} {
if {[regexp {^([a-z]+)::(.*)} $cmd -> type name]} {
if {$type in {object array}} {
# Create an appropriate elementNode
namespace eval eval [list tdom::fsnewNode \
-jsonType [string toupper $type] $name {*}$args]
} else {
# Create an appropriate elementNode
namespace eval eval [list tdom::fsnewNode \
-jsonType NONE $name [linsert $args 0 $type]]
}
}
}
proc build {type script {data {}}} {
dom createDocumentNode json
# Make the data dict available as variables in the eval namespace
namespace eval eval [list variable {*}$data]
try {
namespace eval eval \
[list [namespace which $json] appendFromScript $script]
} finally {
# Clean up the variables from the data dict
namespace eval eval [list unset {*}[dict keys $data]]
}
$json jsonType [string toupper $type]
$json asJSON -indent 2
}
}
# Demo
set script {
number::aNumber $number
string::aNumberAsString $number
string::aString $str
boolean::aBool $bool
"string::a key with spaces" "It's possible."
array::aArray {
string $chra
string $chrb
string $chrc
}
object::aObject {
string::foo $foo
string::bar $bar
}
array::aObjectArray {
object {
string::foo objArray_01_foo
string::bar objArray_01_bar
array::aArray {
string five_01_a
string five_01_b
string five_01_c
}
}
object {
string::foo objArray_02_foo
string::bar objArray_02_bar
array::aArray {
string five_02_a
string five_02_b
string five_02_c
}
}
}
}
set data {
number 0.123
str "this is a string"
bool yes
chra a
chrb b
chrc c
foo that
bar grill
}
puts [json build object $script $data]