Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Construct literal objects with subsequences of key-value pairs #2529

Closed
phs opened this issue Jan 24, 2023 · 8 comments
Closed

Construct literal objects with subsequences of key-value pairs #2529

phs opened this issue Jan 24, 2023 · 8 comments

Comments

@phs
Copy link

phs commented Jan 24, 2023

Describe the bug

I want a syntax to construct objects with subsequences of key-value pairs.

To Reproduce

Consider this construction with an array:

$ jq -n '["foo", (["bar", "baz"] | .[] | (. + "X")), "quux"]'
[
  "foo",
  "barX",
  "bazX",
  "quux"
]

We use a parenthesized expression within a literal array that expands to a sequence of values, which are then inlined into the array.

I would like to do the same with key-value pairs within literal objects. Consider this example:

$ jq -n '{"foo": "foo", (["bar", "baz"] | .[] | (.: .)), "quux": "quux"}'
jq: error: syntax error, unexpected ':' (Unix shell quoting issues?) at <top-level>, line 1:
{"foo": "foo", (["bar", "baz"] | .[] | (.: .)), "quux": "quux"}                                         
jq: 1 compile error

Expected behavior

Instead of erroring out, I would like to see this output:

{
  "foo": "foo",
  "bar": "bar",
  "baz": "baz",
  "quux": "quux"
}

Environment (please complete the following information):

jq 1.6 on Pop!_OS 22.04

@itchyny
Copy link
Contributor

itchyny commented Jan 24, 2023

map({(.): .}) | add will help you.

 $ jq -n '["foo", "bar", "baz", "quux"] | map({(.): .}) | add'
{
  "foo": "foo",
  "bar": "bar",
  "baz": "baz",
  "quux": "quux"
}

@phs
Copy link
Author

phs commented Jan 25, 2023

Ok, yea. In reality I'm using jq as a templating language; my expression has null input, is somewhat large, and the object in question is nested a few layers deep. In this sense the outer "foo" and "quux" keys not being in the example array is significant to to the use case.

But this works:

$ jq -n '{ "foo": "foo", } + (["bar", "baz"] | map({(.): .}) | add) + { "quux": "quux" }'
{
  "foo": "foo",
  "bar": "bar",
  "baz": "baz",
  "quux": "quux"
}

One could imagine parenthesizing the inner-most object in my tree, and having some multi-line construction like this:

        # ...
      } +
      (
        ["the", "dynamic", "keys"] |
        map({.: some-function-of(.)}) |
        add
      ) +
      {
        # ...

@phs phs changed the title Construst literal objects with subsequences of key-value pairs Construct literal objects with subsequences of key-value pairs Jan 25, 2023
@itchyny
Copy link
Contributor

itchyny commented Jan 25, 2023

So you can close this issue or you want to discuss on the new syntax? I think the proposed syntax has various difficulties, especially on rejecting invalid queries like [.: .].

@emanuele6
Copy link
Member

emanuele6 commented Jan 25, 2023

You can define your object as an array of key value pairs:

[
    ["key1", "value"],
    ["key2", {foo: "bar"}]
]
# instead of
{
    key1: "value"
    key2: {foo: "bar"}
}

And then call map({ key: .[0], value: .[1] }) | from_entries on it, e.g. for your example:

[
    ["foo", "foo"],
    ["bar", "bar"],
    ("baz", "quux" | [., .])
] |
map({ key: .[0], value: .[1] }) |
from_entries

Calling { key: .[0], value: .[1] } inside the array constructor instead of calling map/1 also works:

[
    ["foo", "foo"],
    ["bar", "bar"],
    ("baz", "quux" | [., .])

    | { key: .[0], value: .[1] }
] |
from_entries

@phs
Copy link
Author

phs commented Jan 25, 2023

So you can close this issue or you want to discuss on the new syntax?

My immediate need has been addressed, we can close.

sits back in armchair

As a hypothetical, the idea is still fun to kick around.

I think the proposed syntax has various difficulties, especially on rejecting invalid queries like [.: .].

Right. As a first step, we could ask "What is the jv_kind of a _ : _?" We could guess it is a JV_KIND_OBJECT, but then we may have a hard time distinguishing [.: .] from (legal) [{.: .}]. Instead it might be a new kind, something like JV_KIND_PAIR or JV_KIND_KEY_VALUE.

Type checks would then prevent values of the new kind from appearing in most contexts. Your [.: .] case for example could be caught by a hypothetical check_array_entry called by the array parser similar to check_object_key for objects.

@phs phs closed this as completed Jan 25, 2023
@wader
Copy link
Member

wader commented Jan 25, 2023

When i was messing with jqjq i was thinking a bit why only array syntax can "collect" but not object, would be useful if it could i think. Haven't thought deeply about possible syntax for it, would it help if the syntax was something like:

{"foo": "foo", ("bar", "baz" | {.: .}), "quux": "quux"}

a key-less expression (i guess parentheses would be required?) inside an object literal can output objects that will be merged?

@itchyny
Copy link
Contributor

itchyny commented Jan 25, 2023

@wader What happens when you extract the query to declaration?

jq -n '{f:1,g:2,h:3}; {f}' # { f: 1 }
jq -n '{f:1,g:2,h:3} | def f: "x"; {(f):1}' # { x: 1 }
jq -n '{f:1,g:2,h:3} | def f: {x:0}; {(f)}' # looks close and currently error, but should be {x:0}???

@wader
Copy link
Member

wader commented Jan 25, 2023

Aha sorry that example was not what i intended, in the case @phs want, key and value from dot it would have to be like this to use current object syntax:

{"foo": "foo", ("bar", "baz" | {(.): .}), "quux": "quux"}

jq -n '{f:1,g:2,h:3}; {f}' # { f: 1 }

Now i see, yes that is not good. Would requiring parentheses help? ex:{f:1,g:2,h:3} | {(f)} would be {f:1,g:2,h:3}.

jq -n '{f:1,g:2,h:3} | def f: "x"; {(f):1}' # { x: 1 }

Would not be a key-less "pair"? so no change

jq -n '{f:1,g:2,h:3} | def f: {x:0}; {(f)}' # looks close and currently error, but should be {x:0}???

Yeap would be {x:0} in my proposal.

To add an example {({a:1, b:2}), ({b: 22, c: 3})} would be {"a":1,"b":22,"c":3}.

Sorry for just shooting syntax proposals without much thought :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants