jqstartswith

How to use .fieldname to jq's startswith


In the jq script below I want to print either the concatenation of the first_name and the last_name or just the last_name.

$ echo '{"first_name": "John", "last_name": "Johnson"}' | jq -c '{
  "name": (if (.last_name | startswith(.first_name)) 
           then .last_name 
           else .first_name + " " + .last_name 
           end)
}'

The error is

jq: error (at <stdin>:1): Cannot index string with string "first_name"

The jq command fails in the call to startswith - as if startswith only accepted string literals. If I change startswith(.first_name) to startswith("John") then the expression compiles and works as expected.

In the real-world example, there is many input records and many different first_names. Is there a way I could plug in .first_name to startswith?


Solution

  • You're using startswith right, but with the preceding | you're starting a new context (holding the value of .last_name) in which .first_name isn't valid anymore.

    Generally speaking, to reference a value at a later time (when it has gone out of context), bind a variable to it when it's still in context, and use that instead later:

    if .first_name as $fn | .last_name | startswith($fn) then …
    

    You may also bind the whole object at an earlier stage (e.g. if you happen to need multiple references to it or its parts):

    . as $obj | {name: (if $obj.last_name | startswith($obj.first_name) then …)}
    

    Or define your own function that takes two parameters (e.g. if you need this construct more often):

    # taking two values as parameters which retain their binding
    def startswith($whole; $part): $whole | startswith($part);
    
    # or carrying over the context to allow for functional parameters
    def startswith(whole; part): . as $ctx | whole | startswith($ctx | part);
    
    # then
    {
      name: (
        if startswith(.last_name; .first_name)
        then .last_name else "\(.first_name) \(.last_name)"
        end
      )
    }