I have a dictionary which is sometimes nested, sometimes not. So I
need to test whether a value in the nested dict is there before
attempting to do a dict get.
eg.
> set dict [dict create a [dict create c 3] b 2]
a {c 3} b 2
> dict exists $dict a c
1
All good.. but when the nested dict is not there, I get an error doing
the same test:
> dict set dict a 1
a 1 b 2
> dict exists $dict a c
missing value to go with key
I would have liked this just to return a 0 instead of an error, but as
it stands, to check if the nested value exists and avoid the
possibility of an error I need to something along the lines of:
> if { [dict exists $dict a] && [llength [dict get $dict a] > 1] && [dict exists $dict a c] } {
....
}
Seems a bit cumbersome.. how do most folk get round this?
> info patchlevel
8.5.8
"dict exists" tries to convert the data to test the existence of a key
on into a dictionary!
So ...
dict exists [dict create a 1 b 2] a c
... will first try to do ...
dict get {a 1 b 2} a c
... which is the same like ...
dict get [dict get {a 1 b 2} a] c
... which is the same like ...
dict get 1 c
... which causes the error:
"missing value to go with key"
"dict exists" only works on real dictionaries or lists with an even
count of elements.
So it is not the right thing to test for a nested dictionary!
Something like the following code could be used to test, if data is
usable as dictionary:
proc stringIsDict {string} {
# loosely tested an empty string is a dictionary
#
if {$string eq ""} {
return 1;
}
# parsable as list, than parsable as dict
#
if {![string is list $string]} {
return -code ok -errorcode [list EINVAL "not parsable as
list or dict"] 0;
}
# an even count of elements used as pairs of key and value
#
if {[llength $string] % 2 != 0} {
return -code ok -errorcode [list EINVAL "no even count of
elements to represent key-value-pairs"] 0;
}
# unique keys
#
set keys [list];
foreach {key value} $string {
if {$key in $keys} {
return -code ok -errorcode [list EINVAL "duplicate key
\"$key\" found"] 0;
}
lappend keys $key;
}
return 1;
}
After applying "stringIsDict" you could access the data/string as
dictionary.
Best regards,
Martin
I guess I was making assumptions from the description of it:
"[dict exists] returns a boolean value indicating whether the given
key (or path of keys through a set of nested dictionaries) exists in
the given dictionary value. This returns a true value exactly when
dict get on that path will succeed. "
When it says it will return a true value exactly when dict get
succeeds, I made the mistake of assuming the converse would also be
true: i.e. it will return false exactly when dict get would fail for
the path in question. Hypothetically, would that not seem like more
consistent behavior?
Yes!
I would even assume, that "dict exists" on an invalid dict should
return 0 or an appropriate error message, which would not be the same,
than that from "dict get".
Best regards,
Martin
Any Tcl command with "exists" as the subcommand should return 0 or 1
unless some error occurs during argument interpretation. But in the
case of [dict] all I can say is that it is a crap shoot. [dict exists]
means does the dict key exist, not does the dict exist. Of course the
key could exist just because the value of the parent key has an even
number of words:
{To Kill a Mockingbird} is a dict, but {Titanic} is not.
The central issue with a dict structure is that there is no way to
distinguish key from value, and dict operations which lappend or even
append to values can create the appearance of a new dict, or destroy
one.
IMHO, this makes the dict structure fragile outside of a surrounding
API (not the dict api) which maintains it. This is similar to the
problem of interpreting strings as lists, but slightly worse. For one
thing, dict commands cannot operate on nested keys, even though they
can access the nested key value. You can't append or lappend to a
nested dict key value.
The root of the problem is tcl's EIAS requirement, which means if you
can't tell by inspecting a string visually and tell what is a dict and
what isn't they nether can some piece of code. At any point during
execution you are free to use a string as a dict or not, there isn't
anything inside tcl that carries around type information that says
something is a dict. If you want to "solve" this problem that I would
suggest you add type information to your dict strings so you can tell
when a value is a dict and when it isn't. This of course leads to an
API as mentioned about.
tomk
Maybe there is a missing subcommand here. [dict exists] requires that
the user passes a dictionary value and a key path into a valid
dictionary.
There are several alternatives: [dict pathexists], which returns 0/1
if the path leads to a dictionary (like array exists, which doesn't
report if a particular key exists).
Problems with the current API are that you can't append or lappend to
nested dict keys, but you can set a nested value. There is no way,
without using [catch] to discover if a dictionary value is itself a
dict. So maybe a [dict isDict] is needed. Otherwise, users should not
make any assumptions about a dictionary value, instead create an API
to maintain and access a dictionary. Of course, if you have to create
a special API to protect and maintain a dictionary, what is the point
of making it portable?