Skip to content
VLSI Mentor

Tcl Part I · Module 7 · Dictionaries and Arrays

Converting Between Arrays and Dictionaries

You will not often get to pick one structure and keep it. Existing code hands you arrays, new code wants dictionaries, and the two have to meet somewhere. Happily the bridge is two commands you already know, because array get produces exactly the shape a dictionary is. This closing chapter shows the conversion in both directions, confirms what survives it — more than you might expect, including nested structure — and is precise about the single thing it destroys, which is not reversible and not reported.

Intermediate11 min readTclPart IDictionariesArraysMigration

Part I · Module 7 · Chapter 7.12 · Converting Between Arrays and Dictionaries

1. The bridge is a shape, not a command

There is no array to dict command, and none is needed. array get returns a flat list of alternating names and values — which is precisely what a dictionary is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set limits {retries 3 timeout 60 mode fast}

set d [array get limits]
puts "as text   : $d"
puts "as a dict : [dict size $d] pairs, retries = [dict get $d retries]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
as text   : mode fast retries 3 timeout 60
as a dict : 3 pairs, retries = 3

No conversion happened. array get produced a string, and dict get read that string by dictionary rules — the point Chapter 7.1 §4 made about a dictionary being its own text.

The other direction is array set:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set cfg [dict create mode fast runs 3]
array set opt $cfg
puts "opt(mode) = $opt(mode)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
opt(mode) = fast

2. What survives: more than you would guess

Keys containing spaces:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set sp [dict create "max fanout" 16 ok 1]
array set a2 $sp

puts "names : [array names a2]"
puts "read  : $a2(max fanout)"
puts "back  : [array get a2]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
names : ok {max fanout}
read  : 16
back  : ok 1 {max fanout} 16

The automatic quoting from Chapter 6.2 carries the key through intact. Note that $a2(max fanout) reads fine unquoted — substitution takes everything up to the closing parenthesis as the index, so only the literal set form needed grouping back in Chapter 7.8 §3.

Values containing spaces:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set vs [dict create note "two words" n 1]
array set a3 $vs
puts "element: '$a3(note)'"
puts "back   : [array get a3]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
element: 'two words'
back   : n 1 note {two words}

Nested dictionaries, which is the surprising one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set nest [dict create outer [dict create inner 1] flat 2]
array set a4 $nest

puts "a4(outer) : '$a4(outer)'"
puts "usable    : [dict get $a4(outer) inner]"
puts "back      : [array get a4]"
puts "deep get  : [dict get [array get a4] outer inner]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a4(outer) : 'inner 1'
usable    : 1
back      : flat 2 outer {inner 1}
deep get  : 1

The nested dictionary became the string value of one element, and because it is still a well-formed dictionary string, dict get reads straight through it — even after converting back. The array never understood the nesting; it did not need to.

Empty converts both ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set a6 [dict create]
puts "dict->array: exists [array exists a6], size [array size a6]"

array set a7 {}
puts "array->dict: '[array get a7]', size [dict size [array get a7]]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict->array: exists 1, size 0
array->dict: '', size 0

3. What does not survive: order

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set ordered [dict create z 1 y 2 x 3 w 4 v 5]
puts "original     : [dict keys $ordered]"

array set tmp $ordered
puts "via array    : [array names tmp]"
puts "converted back: [dict keys [array get tmp]]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
original     : z y x w v
via array    : x y v z w
converted back: x y v z w

The dictionary knew its keys were z y x w v. The array does not keep an order (Chapter 7.10 §4), so the moment the data passed through it, that information was gone — and converting back does not bring it back, because there is nothing left to bring back.

If the order matters, carry it separately:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set ordered [dict create z 1 y 2 x 3]
set key_order [dict keys $ordered]

array set tmp $ordered

# ... array-based code does its work ...

set rebuilt [dict create]
foreach k $key_order {
    if {[info exists tmp($k)]} {
        dict set rebuilt $k $tmp($k)
    }
}
puts [dict keys $rebuilt]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
z y x

Or, more simply: sort. An arbitrary order is unacceptable in output, and alphabetical is at least reproducible.

4. The merge hazard

Converting a dictionary into an array uses array set, and array set merges (Chapter 7.9 §3). Converting into an array that already has contents does not replace them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set a5 {old 1}
array set a5 [dict create new 2]
puts [lsort [array names a5]]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
new old

old is still there. In a loop that converts one record at a time into the same array, every record inherits every previous record's leftovers:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set records [list [dict create n u1 w 8] [dict create n u2]]

foreach r $records {
    array unset rec
    array set rec $r
    puts "[array get rec]"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
n u1 w 8
n u2

The array unset rec is what makes the second record correct. Without it, u2 would appear to have a width of 8 inherited from u1 — a result that is plausible, wrong, and very hard to spot.

5. The practical pattern: a seam

The reason to convert is usually to put a boundary between old and new code. The clean shape is to convert at the edge and work in one form inside:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# Old code: fills a caller's array
proc legacy_read_config {name} {
    upvar 1 $name cfg
    array set cfg {mode fast retries 3 timeout 60}
}

# New code: takes and returns a dictionary
proc apply_policy {cfg} {
    if {[dict get $cfg mode] eq "fast"} {
        dict set cfg timeout [expr {[dict get $cfg timeout] / 2}]
    }
    return $cfg
}

# The seam
array unset raw
legacy_read_config raw
set cfg [apply_policy [array get raw]]

foreach k [lsort [dict keys $cfg]] {
    puts [format "  %-8s %s" $k [dict get $cfg $k]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mode     fast
  retries  3
  timeout  30

One conversion, at one line, in one direction. The new procedure is a function on values and knows nothing about arrays; the old one is untouched. That is what migrating a boundary at a time looks like, as Chapter 7.11 §7 recommended.

And the other direction, when a new-style result must go back to old-style code:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc legacy_report {name} {
    upvar 1 $name a
    foreach k [lsort [array names a]] { puts "  $k = $a($k)" }
}

array unset out
array set out $cfg
legacy_report out
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mode = fast
  retries = 3
  timeout = 30

6. A quick reference

DirectionCommandNotes
array → dictset d [array get a]always valid; order is arbitrary
array → dict, filteredset d [array get a pat]glob on element names
dict → arrayarray set a $dmerges; array unset a first
dict → array, fresharray unset a ; array set a $dthe safe form
test before convertingarray exists anot info exists a

7. Exercises

Exercise 1. Why does dict get [array get limits] retries work with no conversion step?

Exercise 2. After this, what is [lsort [array names a]], and why is it not just x?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set a {old 1}
array set a [dict create x 2]

Exercise 3. A dictionary built as z y x comes back from a round trip through an array as something else. What exactly was lost, and can it be recovered from the result?

Exercise 4. Write a loop over three records that converts each into the same array and prints it, without any record inheriting the previous one's keys.

Exercise 5. Write array_to_sorted_dict — returns a dictionary built from an array with its keys in alphabetical order, so that the result at least has a reproducible order.

Exercise 6. Show that a nested dictionary survives a round trip through an array, by reading a two-level value out of the converted result.

Answers

1. Because array get returns a flat list of alternating names and values, and that is exactly what a dictionary is. There is nothing to convert — dict get simply reads the same string by dictionary rules.

2. old x. array set merges into whatever is already there rather than replacing it, so old survives.

3. The order of the keys. It cannot be recovered from the result, because an array never stored it — the converted dictionary is complete and correct in every other respect. The only fix is to have kept dict keys beforehand, or to impose a fresh order such as lsort.

4.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set records [list \
    [dict create n u1 w 8] \
    [dict create n u2] \
    [dict create n u3 w 32 d 4]]

foreach r $records {
    array unset rec
    array set rec $r
    puts "[lsort [array names rec]]"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
n w
n
d n w

The second record has only n, which is the point — without array unset it would have shown n w.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc array_to_sorted_dict {name} {
    upvar 1 $name a
    set d [dict create]
    foreach k [lsort [array names a]] {
        dict set d $k $a($k)
    }
    return $d
}

array set lim {retries 3 timeout 60 mode fast}
puts [array_to_sorted_dict lim]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mode fast retries 3 timeout 60

Because a dictionary preserves insertion order, inserting in sorted order makes the result's order both defined and alphabetical.

6.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set nest [dict create outer [dict create inner 1] flat 2]
array set a4 $nest
puts [dict get [array get a4] outer inner]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1

The nested dictionary rode through as the string value of one element, and it is still a valid dictionary string on the way out.

8. What Module 7 covered

Twelve chapters, two structures, one rule applied throughout:

  • The difference between a value and a family of variables, and why it decides everything
  • Dictionaries: dict create, set, get, exists, keys, values, size, for, nesting with key paths, and with / update
  • Arrays: elements as variables, array set / get / names / exists / size / unset, and parray
  • The ordering guarantee one has and the other does not
  • Choosing between them on four axes, with the performance question measured rather than assumed
  • Converting between them, and the one thing conversion destroys

The thread is the one Chapter 7.1 started with: a dictionary is a value, so it passes, returns, nests and keeps its order; an array is a set of variables, so it does none of those and in exchange gives you ordinary variable syntax on names computed at run time. Every difference in the module follows from that single fact.

9. What comes next

Module 8 is Conditions and Control Flow. This module has leaned on if in almost every chapter without ever teaching it properly — the braces around the condition, the braces around the body, and why both are mandatory rather than stylistic. Module 8 settles that, then adds switch with its three matching modes and its fallthrough rule.

It also closes a loop opened back in Chapter 3.6: a value that is neither true nor false is an error in a condition, not a quiet false — and the honest test for a value that is missing rather than wrong is exactly the dict exists and info exists discipline you have just spent twelve chapters practising.

10. Remember

  • array get produces the shape a dictionary is, so no conversion command is needed in that direction.
  • array set a $dict goes the other way — and it merges, so array unset a first whenever the array may not be empty.
  • Reusing one array in a loop without array unset is the classic bug: records silently inherit the previous record's keys.
  • Keys and values containing spaces survive the trip, thanks to automatic quoting.
  • Nested dictionaries survive as string values, and remain deep-readable after converting back.
  • Order does not survive. It is destroyed silently, one way, and cannot be recovered from the result.
  • If order matters, save dict keys first or impose lsort on the way out.
  • Convert at a seam — one line, one direction — and work in a single form on each side of it.

Where this fits

Part of the Tcl curriculum.