JSONPath-style access to nested data

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]