1

I know about How to create JSON from associative array but that's not my problem.

I have this associative array:

declare -A aliases
aliases[Index]=components/Index/Exports
aliases[Shared]=components/Shared/Exports
aliases[Icons]=components/Icons/Exports

Now I need to convert this associative array into this JSON:

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "Index": ["components/Index/Exports"],
            "Shared": ["components/Shared/Exports"],
            "Icons": ["components/Icons/Exports"],
        }
    }
}

I want to use jo and jq. But I can't come up with the nesting.

I tried this code:

jo -p compilerOptions[baseUrl]=. compilerOptions[paths]="$(jo -p a ${Aliases[@]})"

But it does not even run.

2 Answers 2

2

One possible solution to this:

declare -A aliases
aliases[Index]=components/Index/Exports
aliases[Shared]=components/Shared/Exports
aliases[Icons]=components/Icons/Exports

jq -n --argjson n "${#aliases[@]}" '
        { compileroption: {
                baseurl: ".",
                paths:
                (
                        reduce range($n) as $i ({};
                                .[$ARGS.positional[$i]] = [$ARGS.positional[$i+$n]]
                        )
                )
        } }' --args "${!aliases[@]}" "${aliases[@]}"

Does not use jo and instead pass the keys and values of the associative array aliases into jq as positional parameters with --args at the end of the command (--args must always be the last option, if it's used at all). The jq utility receives the keys and values as a single array, $ARGS.positional. This means the first half of the array contains the keys, and the second half of the array contains the corresponding values.

The body of the jq expression creates the output object and uses a reduce operation over a range of $n integers from zero up, where $n is the number of elements in the aliases array. The reduce operation builds the paths object by adding the positional arguments, one by one, using $i:th argument as the key and the $i+$n:th argument as the element in the corresponding array value.


A slightly different approach using jo to create leaf objects of each key-value pair of the associative array:

declare -A aliases
aliases[Index]=components/Index/Exports
aliases[Shared]=components/Shared/Exports
aliases[Icons]=components/Icons/Exports

for key in "${!aliases[@]}"; do
        jo "$key[]=${aliases[$key]}"
done

This would output the three objects

{"Icons":["components/Icons/Exports"]}
{"Index":["components/Index/Exports"]}
{"Shared":["components/Shared/Exports"]}

Since we're using jo like this, we impose some obvious restrictions on the keys of the array (may not contain =, [] etc.)

We could use jq in place of jo like so:

for key in "${!aliases[@]}"; do
        jq -n --arg key "$key" --arg value "${aliases[$key]}" '.[$key] = [$value]'
done

We may then read these and add them in the correct place in the object we're creating in jq:

declare -A aliases
aliases[Index]=components/Index/Exports
aliases[Shared]=components/Shared/Exports
aliases[Icons]=components/Icons/Exports

for key in "${!aliases[@]}"; do
        jo "$key[]=${aliases[$key]}"
done |
jq -n '{ compileroptions: {
        baseURL: ".",
        paths: (reduce inputs as $item ({}; . += $item)) } }'

The main difference here is that we don't pass stuff into jq as command line options, but rather as a stream of JSON objects.

3
  • This works. But man, that code is so difficult. Even after years of experience I still could not write that code, let alone maintain it. Thank you so much. Jun 30 at 15:32
  • @SaeedNeamati It's my first stab at an answer for your issue. There may definitely be simpler solutions, but I would need to come back to it later to see the problem afresh. In the meanwhile, someone else may provide an improved answer.
    – Kusalananda
    Jun 30 at 15:35
  • @SaeedNeamati See updated answer.
    – Kusalananda
    Jun 30 at 22:55
1

Personally, I'd use perl or other proper programming language instead of a shell (especially bash!). Or at least switch to zsh with a better associative array support and use perl to do the JSONy stuff:

#! /usr/bin/perl
use JSON;

%my_aliases = (qw(
  Index   components/Index/Exports
  Shared  components/Shared/Exports
  Icons   components/Icons/Exports
));

$j->{compilerOptions}->{baseUrl} = "";
$j->{compilerOptions}->{paths}->{$_} = [$my_aliases{$_}] for keys%my_aliases;
print to_json($j, {"pretty" => 1});

Or:

#! /bin/zsh -

typeset -A my_aliases=(
  Index   components/Index/Exports
  Shared  components/Shared/Exports
  Icons   components/Icons/Exports
)

print -rNC1 -- "${(kv@)my_aliases}" |
  perl -MJSON -0e '
    chomp (@records = <>);
    %my_aliases = @records;
    $j->{compilerOptions}->{baseUrl} = "";
    $j->{compilerOptions}->{paths}->{$_} = [$my_aliases{$_}] for keys%my_aliases;
    print to_json($j, {"pretty" => 1})'
2
  • I think Perl support might not be widespread in docker images. That's why I chose to do it with shell. Jun 30 at 18:31
  • 1
    @SaeedNeamati, I'd expect perl or python to be more common than jo or jq though. Jun 30 at 19:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.