在TCL中创建dict中的dicts列表

时间:2014-09-12 18:54:44

标签: json dictionary tcl

我被迫使用TCL,我需要创建一个像这样的json字符串:

{ "mainKey": "mainValue", "subKey": [{"key1":"value1"},{"key2":"value2"}]}

所以我想这样做:

set subDict1 [dict create key1 value1]
set subDict2 [dict create key2 value2]
set subDictList [list $subDict1 $subDict2]
set finalDict [dict create mainKey mainValue subKey $subDictList]

当我将此dict转换为json时,我得到:

{"mainKey":"mainValue", "subKey":{"key1 value1":{"key2":"value2"}}} 

而不是必需的:

{ "mainKey": "mainValue", "subKey": [{"key1":"value1"},{"key2":"value2"}]}

我做错了什么?

2 个答案:

答案 0 :(得分:4)

首先,您必须了解TCL是一种非常无类型的语言。什么是tcl中的列表和词组?

在Tcl中,列表是一个格式正确的字符串,其中列表的每个成员由空格(空格,制表符或换行符)分隔,如果项目包含的数据包含空格,则可以通过以下方式对其进行转义: p>

  1. 使用反斜杠转义:

    "this is a list\ of\ four\ items"
    
  2. 使用""分组:

    {this is a "list of four items"}
    
  3. 使用{}分组:

    {this is a {list of four items}}
    
  4. 请注意,在内部,一旦字符串被解析为列表,Tcl使用不同的内部数据结构来存储列表以提高速度。但从语义上讲,它仍然是一个字符串。就像HTML是一个特殊格式的字符串或JSON是一个特殊格式的字符串Tcl采取的态度列表只是特殊格式的字符串。

    那么,什么是dicts?在Tcl中,dicts是具有偶数元素的列表。而已。没什么特别的。因此,dict在语义上也是一个字符串(尽管如上所述,一旦tcl看到你将该字符串用作dict,它会将其编译为不同的数据结构以优化速度。)

    再次注意tcl中的核心哲学:几乎所有数据结构(数组除外)都只是字符串,恰好以具有特殊含义的方式进行格式化。

    这就是你无法将tcl数据结构自动转换为JSON的原因 - 如果你要求Tcl猜测数据结构是什么,你最终会得到编写猜测函数的程序员想要的东西。在您的情况下,它看起来默认始终检测具有偶数个元素的列表作为dicts。

    那么如何正确生成JSON?

    有几种方法可以做到这一点。您当然可以使用自定义专用for循环或函数将您的数据结构(再次,只是一个特殊格式的字符串)转换为JSON。

    几年前我写了这个JSON编译器:

    # data is plain old tcl values
    # spec is defined as follows:
    # {string} - data is simply a string, "quote" it if it's not a number
    # {list} - data is a tcl list of strings, convert to JSON arrays
    # {list list} - data is a tcl list of lists
    # {list dict} - data is a tcl list of dicts
    # {dict} - data is a tcl dict of strings
    # {dict xx list} - data is a tcl dict where the value of key xx is a tcl list
    # {dict * list} - data is a tcl dict of lists
    # etc..
    proc compile_json {spec data} {
        while [llength $spec] {
            set type [lindex $spec 0]
            set spec [lrange $spec 1 end]
    
            switch -- $type {
                dict {
                    lappend spec * string
    
                    set json {}
                    foreach {key val} $data {
                        foreach {keymatch valtype} $spec {
                            if {[string match $keymatch $key]} {
                                lappend json [subst {"$key":[
                                    compile_json $valtype $val]}]
                                break
                            }
                        }
                    }
                    return "{[join $json ,]}"
                }
                list {
                    if {![llength $spec]} {
                        set spec string
                    } else {
                        set spec [lindex $spec 0]
                    }
                    set json {}
                    foreach {val} $data {
                        lappend json [compile_json $spec $val]
                    }
                    return "\[[join $json ,]\]"
                }
                string {
                    if {[string is double -strict $data]} {
                        return $data
                    } else {
                        return "\"$data\""
                    }
                }
                default {error "Invalid type"}
            }
        }
    }
    

    (有关JSON解析的原始实现和讨论,请参阅http://wiki.tcl.tk/JSON

    因为tcl永远无法正确猜出你的“字符串”是什么,所以我选择为函数提供一个格式字符串,以便正确解释tcl数据结构。例如,使用上面的函数来编译你的dict,你可以这样称呼它:

    compile_json {dict subKey list} finalDict
    

    我已经请求tcllib维护者窃取我的代码,因为我仍然认为这是在tcl中处理JSON的正确方法,但到目前为止它还没有在tcllib中。

    BTW:我将上述代码作为公共域名授权,如果您愿意,您或任何人可以声明其完整的作者身份。

答案 1 :(得分:2)

说Tcl是无类型语言并不完全错误,因为Tcl程序中的数据对象类型并未在代码中完全表达,并且在{{{ 1}}内部表示数据对象的结构。尽管如此,Tcl程序当然不会缺少类型,只是类型系统在Tcl中的侵入性比大多数其他编程语言要少得多。

Tcl程序中的完整类型定义是在程序执行时从代码和数据对象的动态组合中产生的。解释器相信您可以告诉它您希望数据对象的行为方式。

例如,请考虑以下字符串:

Tcl_Obj

这是字符串,数组还是字典?以上所有,实际上。 (至少它不是整数,双精度或布尔值,其他可能的Tcl类型。)

使用此字符串,我可以回答许多问题:

告诉我你的名字

set s {title: Mr. name: Peter surname: Lewerin}

有礼貌的人会给你打电话什么?

puts $s
# => title: Mr. name: Peter surname: Lewerin

你的姓氏又是什么?

puts [dict values $s]
# => Mr. Peter Lewerin

在这里,我使用相同的字符串作为字符串,字典和数组。所有三种类型的对象都使用了相同的字符串表示,而我在其上使用的操作确定了该精确时刻内对象的类型。

类似地,文字puts [lindex $s end] # => Lewerin 可以表示整数1,单字符串1或布尔真值。没有办法指明你的意思是哪一种,但也没有必要,因为翻译不会抱怨模棱两可。

因为Tcl不存储完整的类型信息,所以很难序列化任意数据对象集合。但这并不意味着Tcl在序列化方面表现不佳:您只需要为数据添加注释。

此字符串:

1

可以输入到Tcl解释器中,并给出di [dm [st mainKey] [st mainValue]] [dm [st subKey] [ar [di [dm [st key1] [st value1]]] [di [dm [st key2] [st value2]]]]] didmst的正确定义(我打算表示#34} ;字典","字典成员","字符串"和"数组",分别),我可以让字符串构造一个等效于字典结构的字典结构问题中的一个,或者这样一个对象的字符串表示,只是一个简单的键和值列表,或XML或JSON等。通过使用命名空间和/或从解释器,我甚至可以在各种表单之间动态切换。我不会为所有表单提供示例,只有JSON:

ar

输出变为:

proc di args {return "{[join $args {, }]}"}
proc st val {return "\"$val\""}
proc ar args {return "\[[join $args {, }]]"}
proc dm {k v} {return "$k: $v"}

此示例使用Tcl解释器的命令嵌套来定义数据的结构。 Tcl甚至不需要:令牌类和令牌列表(如扫描仪会发出)就足够了:

{"mainKey": "mainValue", "subKey": [{"key1": "value1"}, {"key2": "value2"}]}

使用这些简单的命令:

< : ' mainKey ' mainValue : ' subKey ( < : ' key1 ' value1 > < : ' key2 ' value2 > ) >

我可以将令牌类的流(&lt;,(,&#39;,:),&gt;)和令牌解析为与上面相同的JSON字符串:

proc jsonparseseq {endtok args} {
    set seq [list]
    while {[lsearch $args $endtok] > 0} {
        lassign [jsonparseexpr {*}$args] args expr
        lappend seq $expr
    }
    list [lassign $args -] $seq
}

proc jsonparseexpr args {
    set args [lassign $args token]
    switch -- $token {
        ' {
            set args [lassign $args str]
            set json \"$str\"
        }
        : {
            lassign [jsonparseexpr {*}$args] args key
            lassign [jsonparseexpr {*}$args] args val
            set json "$key: $val"
        }
        < {
            lassign [jsonparseseq > {*}$args] args dict
            set json "{[join $dict {, }]}"
        }
        ( {
            lassign [jsonparseseq ) {*}$args] args arr
            set json "\[[join $arr {, }]]"
        }
    }
    list $args $json
}

proc jsonparse args {
    lindex [jsonparseexpr {*}$args] end
}

Tcl提供了很大的灵活性;很少有语言可以像程序员那样对程序员做出回应。

为了完整性,我还将演示如何使用slebetman提到的Tcllib huddle包来创建问题中提到的那种结构,并将其序列化为JSON:

jsonparse < : ' mainKey ' mainValue : ' subKey ( < : ' key1 ' value1 > < : ' key2 ' value2 > ) >
# -> {"mainKey": "mainValue", "subKey": [{"key1": "value1"}, {"key2": "value2"}]}

另一种方法是创建常规Tcl结构并转换(&#34;编译&#34;)它们以根据类型规范来挤占数据:

package require huddle
# -> 0.1.5
set subDict1 [huddle create key1 value1]
# -> HUDDLE {D {key1 {s value1}}}
set subDict2 [huddle create key2 value2]
# -> HUDDLE {D {key2 {s value2}}}
set subDictList [huddle list $subDict1 $subDict2]
# -> HUDDLE {L {{D {key1 {s value1}}} {D {key2 {s value2}}}}}
set finalDict [huddle create mainKey mainValue subKey $subDictList]
# -> HUDDLE {D {mainKey {s mainValue} subKey {L {{D {key1 {s value1}}} {D {key2 {s value2}}}}}}}
huddle jsondump $finalDict {} {}
# -> {"mainKey":"mainValue","subKey":[{"key1":"value1"},{"key2":"value2"}]}

最后一个命令的结果与上一个示例中的最后一个set subDict1 [dict create key1 value1] set subDict2 [dict create key2 value2] set subDictList [list $subDict1 $subDict2] set finalDict [dict create mainKey mainValue subKey $subDictList] huddle compile {dict mainKey string subKey {list {dict * string}}} $finalDict 命令的结果相同。

文档:dictjoinlappendlassignlindexlistlsearch,{{3 }},procputsreturnsetswitch