'Is it possible to define a custom "@" escape functions in jq?

We have a number of builtin format/escape functions: @csv, @sh, etc

… | @sh
@sh "\( … )"

It is possible to define a custom format/escape function, say @sql?

jq


Solution 1:[1]

Um. Technically, yes. I don't think I could recommend it, but it does appear to be possible.

First of all, you need to understand that @csv, @sh etc aren't separate functions from jq's point of view. They're all implemented by the format/1 function. This function takes a single string argument that is the name of the format to use, e.g. "csv" or "sh". You can redefine this function, and lo and behold jq will use it for formatting!

jq -n 'def format(fstring): "XXX";[1,2,3]|@json'
"XXX"

Okay, that's not very useful. How about this?

jq -n '
    def format(fstring):
        if fstring=="sparkle" then
            ".:!" + (tostring) + "!:."
        else
            oldformat(fstring)
        end
    ;
    [1,2,3]|@sparkle
'
".:![1,2,3]!:."

It worked! But don't get too excited...

jq -n '
    def format(fstring):
        if fstring=="sparkle" then
            ".:!" + (tostring) + "!:."
        else
            oldformat(fstring)
        end
    ;
    [1,2,3]|@json
'

Uh oh, that just hangs. Although we were hoping to delegate to the original format when we don't know what to do with the format, we actually called our new format function recursively. It just keeps calling itself forever. It looks like we might be out of luck on our extremely cursed quest. However, if we read the jq manual carefully, there is a glimmer of hope:

Multiple definitions using the same function name are allowed. Each re-definition replaces the previous one for the same number of function arguments, but only for references from functions (or main program) subsequent to the re-definition. See also the section below on scoping.

Great! We can use this to save the old format function:

jq -n '
    def oldformat(fstring):
        format(fstring);
    def format(fstring):
        if fstring=="sparkle" then
            ".:!" + (tostring) + "!:."
        else
            oldformat(fstring)
        end
    ;
    [1,2,3]|(@sparkle,@json,@sh)
'
".:![1,2,3]!:."
"[1,2,3]"
"1 2 3"

I really don't recommend this: it's awkward to do, not terribly useful, and I have no idea if it's going to break something else. The format function doesn't appear to be documented so it's likely an unsupported implementation detail. But it was fascinating to find out that this is technically possible.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1