14

While trying to create a JSON message for an API, I found myself struggling to do something that I thought would be simple. I needed to create a message like the following:

{ "list": [ { "foo": 1, "bar": 2 } ] }

However, my first attempt did not work:

say to-json { foo => [ { a => 1, b => 2 } ] };
# {"foo":[{"a":1},{"b":2}]}

Trying to simplify things further confused me more:

say { foo => [ { a => 1 } ] };
# {foo => [a => 1]}
# Note that this is not JSON, but I expected to see curly braces

Then I tried to use some temporary variables, and that worked:

my @list = { a => 1 };
say to-json { foo => @list };
# {"foo":[{"a":1}]}

my %hash = ( a => 1 );
say to-json { foo => [ %hash ] };
# {"foo":[{"a":1}]}

What's going on here?

And is there a way I can achieve my desired output without an extra temporary variable?

jja
  • 2,058
  • 14
  • 27
  • 1
    `say to-json { foo => [ { a => 1 } ] };` should output something like `{"foo":[{"a":1}]}`, not `{"foo":["a":1]}`. The latter is a typo, right? If not, what does `say $*PERL.compiler.version;` say? – raiph Feb 07 '20 at 23:27
  • Hm, yeah, you're right. I guess I misread things when I was trying stuff out. Even `say to-json { foo => [ a => 1 ] }` outputs `{"foo":[{"a":1}]}` so who knows what I typed when I got that, if I ever did. My bad! – jja Feb 08 '20 at 00:06

1 Answers1

17

You've discovered the single argument rule. Numerous constructs in Raku will iterate the argument they are provided with. This includes the [...] array composer. This is why when we say:

say [1..10];

We get an array that contains 10 elements, not 1. However, it also means that:

say [[1,2]];

Iterates the [1,2], and thus results in [1,2] - as if the inner array were not there. A Hash iterates to its pairs, thus:

{ foo => [ { a => 1, b => 2 } ] }

Actually produces:

{ foo => [ a => 1, b => 2 ] }

That is, the array has the pairs. The JSON serializer then serializes each pair as a one-element object.

The solution is to produce a single-element iterable. The infix , operator is what produces lists, so we can use that:

say to-json { foo => [ { a => 1, b => 2 }, ] };
#                        note the , here ^

Then the single argument to be iterated is a 1-element list with a hash, and you get the result you want.

Easy way to remember it: always use trailing commas when specifying the values of a list, array or hash, even with a single element list, unless you actually are specifying the single iterable from which to populate it.

Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136