I took a stab at implementing a procedure that takes a data structure along with a JSONPath-style selector and a value to be used as the replacement. I'm still learning Tcl, so there's likely a more performant, or idiomatic way of approaching this, but I figured I would share what I had in an attempt to open a dialog about this approach.
# pset, short for path-set
proc pset {data path val} {
set pathList [split $path .]
set currPath [first $pathList]
set nextPath [join [rest $pathList] .]
# puts "data=$data path=$path currPath='$currPath' val=$val"
set newdata [switch -regexp -matchvar v -- $currPath {
[0-9]+ {
if {[llength $data] < $v} {
puts "Specified path index $v exceeds size of list [llength $data]."
return
}
# Get the element specified at currPath and continue to process it recursively
set selectedData [lindex $data $currPath]
# Return the potentially updated data alongside the other non-related elements in the list
return [lreplace $data $currPath $currPath [pset $selectedData $nextPath $val]]
}
[a-zA-Z]+[0-9]* {
if {[hasf? $data $currPath]} {
#puts "Matching field found."
# If there is more to the path, attempt to dig in further to the fields value
if {[more? $pathList]} {
set fieldValIdx [expr [lsearch $data $currPath] + 1]
return [lreplace $data $fieldValIdx $fieldValIdx [pset [getf $data $currPath] $nextPath $val]]
} else {
# We're at the end of the path. Update the value
#puts "Updating '[getf $data $currPath]' to '$val'"
return [setf $data $currPath $val]
}
} else {
puts "No matching field found at path."
}
}
}]
return $newdata
}
## Helper functions below
# Get the first item of a list
proc first {l} {
lindex $l 0
}
# Get all but the first elements of a list
proc rest {l} {
lrange $l 1 end
}
# Does a list have more than one entry?
proc more? {l} {
expr [llength $l] > 1
}
# "has field"
# Searches a list for a field name
proc hasf? {l f} {
set idx [lsearch $l $f]
return [expr $idx > -1]
}
# "get field"
# Searches a list for a field name and returns
# the next value in the list (the "value")
proc getf {l f} {
set idx [lsearch $l $f]
if { $idx > -1 } {
return [lindex $l [expr $idx + 1]]
}
}
# "set field"
# Searches a list for a field name and updates
# the next value in the list
proc setf {l f v} {
set idx [lsearch $l $f]
if { $idx > -1 } {
set elemAt [expr $idx + 1]
return [lreplace $l $elemAt $elemAt $v]
}
}With the above defined, and data that looks like the following:
set data {
person1 {
name Jim
age 33
address {street "333 Third Street"}
}
person2 {
name Bob
age 32
address {street "123 Fake Street"}
}
}We can update Bob's address like so:
set data [pset $data person2.address.street "One Eleventy 1st Street"]
Or, with data like the following:
set data {
person {
{name Bob}
{name Jim}
{name Dan}
{name Ali}
}
}We could update the name of the last person in "person" like so:
set data [pset $data person.3.name Ally]