0

I'm trying to do something akin to this:

jq -r  '. | ."Time Series (Daily)"."2020-12-02" | ."1. open"' newdata.json

...but with the key coming from a variable, as in:

jq -r --arg key "$key" '. | ."Time Series (Daily)"."[$key]" | ."1. open"' newdata.json

The first one works just fine, but when I assign the date to a variable called key and then try to get the data, it fails.

I tried This answer and This answer. But did not work for me.

{
    "Meta Data": {
        "1. Information": "Daily Prices (open, high, low, close) and Volumes",
        "2. Symbol": "AB",
        "3. Last Refreshed": "2020-12-02",
        "4. Output Size": "Compact",
        "5. Time Zone": "US/Eastern"
    },
    "Time Series (Daily)": {
        "2020-12-02": {
            "1. open": "32.6700",
            "2. high": "33.3300",
            "3. low": "32.5000",
            "4. close": "33.1200",
            "5. volume": "273799"
        },
        "2020-12-01": {
            "1. open": "32.1500",
            "2. high": "32.8000",
            "3. low": "32.0000",
            "4. close": "32.6000",
            "5. volume": "265086"
        },
        "2020-11-30": {
            "1. open": "32.3800",
            "2. high": "32.4900",
            "3. low": "31.7500",
            "4. close": "31.8700",
            "5. volume": "251970"
        }
    }
}

The above is the newdata.json file. What I want to get is the "1. open" value. I am using a for loop to iterate over all the keys of "Time Series (Daily)" and the keys are generated correctly. There is no issue with that. I then want to use the $key variable in each iteration to get the data I need.

readarray keys <<< "$(jq  '."Time Series (Daily)" |  keys[]' newdata.json)"
for key in "${keys[@]}"; do
  jq -r --arg key "$key"  '. | ."Time Series (Daily)" | .[$key] | ."1. open"' newdata.json
done
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
rathindu_w
  • 611
  • 1
  • 5
  • 12
  • Also, [bash !== sh](https://stackoverflow.com/questions/5725296/difference-between-sh-and-bash), please tag accordingly. – 0stone0 Dec 03 '20 at 15:05
  • Now I have updated it. – rathindu_w Dec 03 '20 at 15:13
  • @CharlesDuffy the thing is that I have tried all these different version which were asked by some other stackoverflow users. This was one of those version I tried. – rathindu_w Dec 03 '20 at 15:14
  • 1
    So, the bug you have here is because you didn't use the `-t` argument to `readarray`, so your keys have newlines in them. _And_ you didn't use `-r` on the first `jq`, so they have literal quotes in them too. – Charles Duffy Dec 03 '20 at 16:16
  • You can see that yourself: Run `declare -p keys` and you'll see the output is `declare -a keys=([0]=$'"2020-11-30"\n' [1]=$'"2020-12-01"\n' [2]=$'"2020-12-02"\n')`, instead of `declare -p keys=([0]="2020-11-30" [1]="2020-12-01" [2]="2020-12-02")`. – Charles Duffy Dec 03 '20 at 16:17
  • 1
    BTW, this is in the class of bugs that running `set -x` in your script to log the commands that it invokes would have found for you, since you'd see the `--argstr key "$key"` passing a different key value than intended. – Charles Duffy Dec 03 '20 at 16:18
  • BTW, arrays are a bash-only feature; tag `bash` instead of `sh` when your question uses them. (Same for the `<<<` herestring syntax; POSIX sh only specifies heredocs). – Charles Duffy Dec 05 '20 at 16:30

2 Answers2

1

Use | .[$key] | to get the key from your $key variable;

key="2020-12-02"
jq -r --arg key "$key"  '."Time Series (Daily)" | .[$key] | ."1. open"' newdata.json
# output: 32.6700

Or, combined with the for() (hardcoded keys, since we're not sure how you get those)

keys=("2020-12-02" "2020-12-01" "2020-11-30")
for key in "${keys[@]}"; do
    jq -r --arg key "$key"  '."Time Series (Daily)" | .[$key] | ."1. open"' newdata.json
done

32.6700

32.1500

32.3800

peak
  • 105,803
  • 17
  • 152
  • 177
0stone0
  • 34,288
  • 4
  • 39
  • 64
  • ```readarray keys <<< "$(jq '."Time Series (Daily)" | keys[]' newdata.json)" for key in "${keys[@]}"; do jq -r --arg key "$key" '. | ."Time Series (Daily)" ."\($key)" | ."1. open"' newdata.json done ```This was the code I wrote. But the result is just null values – rathindu_w Dec 03 '20 at 15:34
  • We can't help you debug if you don't provide enough info. What is `readarray`, why did you remove the first `. | ."Time`... – 0stone0 Dec 03 '20 at 15:39
  • 1
    Just `[$key]` works fine without the extra quotes; there's no reason for string interpolation here. – Charles Duffy Dec 03 '20 at 15:44
  • @0stone0, `readarray` is a bash builtin (added in version 4.0; it's a different name for `mapfile`, also added in that same release). – Charles Duffy Dec 03 '20 at 15:46
  • Thanks for the head-up on the string interpolation @CharlesDuffy. Regarding the `readarray`, I ment how OP uses it, since it's not included in the original question. – 0stone0 Dec 03 '20 at 15:51
  • Working fine here. Maybe you could clarify the question on how you want to loop though the keys, and how you get them in the first place, then we're able to reproduce this. – 0stone0 Dec 03 '20 at 15:55
  • 1
    @RathinduWathsala, it absolutely does work when used correctly. If you want to claim it doesn't, you need to show _how you're using it_ that doesn't work so we can see the problem ourselves. – Charles Duffy Dec 03 '20 at 15:56
  • ```readarray metadata <<< "$(jq -r '. | ."Meta Data" | ."1. Information", ."2. Symbol", ."3. Last Refreshed", ."4. Output Size", ."5. Time Zone"' newdata.json)" readarray keys <<< "$(jq '."Time Series (Daily)" | keys[]' newdata.json)" for key in "${keys[@]}"; do jq -r --arg key "$key" '. | ."Time Series (Daily)" | .[$key] | ."1. open"' newdata.json done ```This is the full code section. What I am trying to do is get the "1. open" value for each day. – rathindu_w Dec 03 '20 at 16:03
  • @RathinduWathsala, not in a comment please -- losing newlines means we need to guess where to put command separators. Consider pasting a gist at https://gist.github.com/ or another ad-free pastebin, when it's not appropriate to just edit the question to add more details. (gists can also be checked out and edited with git; you can add multiple files to them, to have the code as one file, the input as another, your actual output as a third, your desired output as a fourth, etc). – Charles Duffy Dec 03 '20 at 16:04
  • I edited the question with the full code. Since I thought it would explain the problem more accurately – rathindu_w Dec 03 '20 at 16:07
  • BTW, why run jq once to get keys and another to get values, instead of having jq just give you both keys and values in one run? – Charles Duffy Dec 03 '20 at 16:08
  • @CharlesDuffy I wanted to retrieve all the fields inside a given date. That is why I used seperate methods to get keys and values. I want them to be saved in a sqlite3 database. – rathindu_w Dec 03 '20 at 16:21
  • "Inside a given date" meaning what, a range? You can still do that -- jq can run regular expressions, for example, or just do string comparisons if you pass separate start and end dates. – Charles Duffy Dec 03 '20 at 16:35
  • That said, I'd probably write this code in Python to have a native sqlite library; much faster to do everything all in one process, and SQL injection vulnerabilities can be avoided when using language bindings that allow bound parameters (to pass data out-of-band from code). – Charles Duffy Dec 03 '20 at 16:36
  • Anyhow, this is well afield of your original question, which AFAICT has been answered thoroughly. – Charles Duffy Dec 03 '20 at 16:39
1

Focusing On The Immediate Issue

The problem isn't how you're passing key to jq; the problem is how you're populating the key variable in the first place.

Change:

readarray keys <<< "$(jq  '."Time Series (Daily)" |  keys[]' newdata.json)"

...to:

readarray -t keys <<< "$(jq -r '."Time Series (Daily)" |  keys[]' newdata.json)"

There are two changes here:

  1. We added the -t argument to readarray, so it no longer includes the newline ending each line in the variable itself.
  2. We added the -r argument to jq, so it no longer adds literal quotes around the strings.

Sidebar: Retrieving both keys and values at the same time

There's no reason to do one pass to retrieve keys and another to retrieve values -- better to just get them all at once:

dates=( )
opening_prices=( )
while IFS=$'\t' read -r date opening_price; do
  dates+=( "$date" )
  opening_prices+=( "$opening_price" )
done < <(
  jq -r '
    ."Time Series (Daily)" | to_entries[] | [.key, .value."1. open"] | @tsv
  ' <newdata.json
)

...after which, declare -p dates opening_prices emits:

declare -a dates=([0]="2020-12-02" [1]="2020-12-01" [2]="2020-11-30")
declare -a opening_prices=([0]="32.6700" [1]="32.1500" [2]="32.3800")

Original response (before population of keys was shown)

Here's a different approach that only calls jq once, instead of once per item, while still getting your keys from an array. It does this by using -R to read raw strings as input; . is then used to address those inputs (which we rename to $key to make it clear how this lines up with the old code).

keys=("2020-12-02" "2020-12-01" "2020-11-30")
readarray -t openingPrices < <(
  jq -Rr --slurpfile indatas newdata.json '
    $indatas[0] as $indata | . as $key |
    $indata."Time Series (Daily)"[$key]["1. open"]
  ' < <(printf '%s\n' "${keys[@]}")
)

After running that, declare -p keys openingPrices (to show how both arrays are defined) emits:

declare -a keys=([0]="2020-12-02" [1]="2020-12-01" [2]="2020-11-30")
declare -a openingPrices=([0]="32.6700" [1]="32.1500" [2]="32.3800")

...so you have an output array that lines up with your input array (so long as the latter isn't sparse).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • This worked for me when I use it in the exact way you gave it. but what I want is not doing this by hard coding the "keys" array. I tried this same exact code using the generated keys array. But that did not work at all. returned null values for the openingPrices. https://gist.github.com/smooth-felix/57a19c76f4bc4e64991b7e54756ee05b check this gist. I have the output I got. – rathindu_w Dec 04 '20 at 06:23
  • As I told you in a comment on the question, there are two things you're doing wrong when you generate `keys`. You can't have literal quotes in the values, and need to tell `readarray` not to sure the newline characters. – Charles Duffy Dec 04 '20 at 13:11
  • (blegh, s/sure/store/; typed that above comment on a phone). – Charles Duffy Dec 04 '20 at 14:32