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]