2

jQuery.ajax() encodes complex JavaScript objects into a query string like this:

?a[b][]=1&a[b][]=2&a[c]=3

I would like to decode this into a Perl data structure like this:

{ a=>{ b=>[1,2], c=>3 } }

PHP does this by default. Is there a way to also do it in a Perl CGI script? Not necessarily using CGI.pm; I can install any library I want.

So far I can only decode one-dimensional arrays because the CGI module's param() function can return an array.

ThisSuitIsBlackNot
  • 23,492
  • 9
  • 63
  • 110
soger
  • 1,147
  • 10
  • 18

2 Answers2

4

Have you considered submitting your data as JSON instead? It's simply a question of replacing

data: data

with

data: JSON.stringify(data)

You could even switch to a POST to avoid URI-encoding, URI-decoding and query size limits (although POST semantics are different than GET semantics).

method:      "POST",
contentType: "application/json; charset=UTF-8",
data:        JSON.stringify(data)

Anyway, the following should do the trick:

use Data::Diver qw( DiveVal );
use List::Util  qw( pairs );  # 1.29+
use URI         qw( );

my $url = URI->new('?a[b][]=1&a[b][]=2&a[c]=3', 'http');

my %data;
PAIR: for ( pairs( $url->query_form() ) ) {
   my ($k, $v) = @$_;

   my ( @k, $push );
   for (my $k_ = $k) {  # Basically C<< my $_ = $k; >>
      s/^(\w+)//
         or warn("Can't handle key $k\n"), next PAIR;

      push @k, $1;
      push @k, $1 while s/^\[(\w+)\]//;
      $push = s/^\[\]//;

      $_ eq ""
         or warn("Can't handle key $k\n"), next PAIR;
   }

   if ($push) {
      push @{ DiveVal(\%data, @k) }, $v;
   } else {
      DiveVal(\%data, @k) = $v;
   }
}

Versions of Perl older than 5.16 need a workaround. Replace

use Data::Diver qw( DiveVal );
push @{ DiveVal(\%data, @k) }, $v;

with

use Data::Diver qw( DiveRef DiveVal );
push @{ ${ DiveRef(\%data, @k) } }, $v;

Note that the format is ambiguous[1]. For example, the following two expressions produce the same result (a[0][x]=3[2]):

jQuery.param( { a: [      { x: 3 } ] } )

jQuery.param( { a: { "0": { x: 3 } } } )

My code will reproduce the former. Numeric keys are always considered to be array indexes.


  1. Another reason to use JSON.
  2. Some escaping removed for readability.
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 2
    Consider turning that into a module? – Schwern Mar 01 '17 at 18:13
  • There's no spec, but the [implementation](https://github.com/jquery/jquery/blob/5f35b5b406ae7d504de86a3f0a5647b2fdf4f2af/src/serialize.js#L55) is pretty short. Of course, it's changed several times over the years... – ThisSuitIsBlackNot Mar 01 '17 at 18:25
  • @ikegami I tried your function, but it says: `Can't use an undefined value as an ARRAY reference at test.pl line 25.` which is the line: `push @{ DiveVal(\%data, @k) }, $v;` I can't say I understand what you are doing there so can you please help me correct it? – soger Mar 02 '17 at 11:17
  • @ikegamy also shouldn't the line `for (my $k_ = $k)` be: `for (my $_ = $k)` I think that's just a typo. – soger Mar 02 '17 at 12:10
  • If one could do `my $_ = $k;`, there wouldn't be any need for `for` at all. `for (my $k_ = $k) { ... }` is used as an alternative to `my $_ = $k; ...` meaning it creates a lexically-scoped `$_` that's a copy of `$k`. – ikegami Mar 02 '17 at 15:26
  • Neat, I did not know about this. Unfortuantely the original problem still stands, the undefined value as ARRAY reference. – soger Mar 02 '17 at 18:01
  • @soger, huh? What version of Perl and Data::Diver do you use? Do you have better luck with `push @{ ${ DiveRef(\%data, @k) } }, $v;`? (Don't forget to import `DiveRef`.) – ikegami Mar 02 '17 at 18:14
  • Also, any reason you're not switching to JSON? – ikegami Mar 02 '17 at 18:20
  • @ikegami DiveRef did it, thank you. I have perl 5.14.2 so not the newest, that may be the problem, but I just installed Data::Diver from CPAN so that's the latest version. Update your response if you wish so it will work for more people. The reason I'm not switching to JSON is because my company extensively uses jquery and php but some tasks are better suited for perl so and I need interoperability. – soger Mar 03 '17 at 10:36
  • That explanation makes no sense. Using JSON actually increases interoperability. JavaScript, PHP and Perl all handle JSON expertly. (`my $data =decode_json($json);`) Unlike this broken format you are asking about. – ikegami Mar 03 '17 at 17:23
  • Well I didn't want to write a novel into the comment section but basically what I'm doing is I'm writing a specialized cache layer between already existing pages that display information on a map (based on postgres notifications it knows when the cache needs to be refreshed) and it's just easier to make my perl code compatible than to rewrite the other pages. And this is not the first time I needed something similar and your code is not that complicated so why not. I know there are better solutions but it's a huge already running project so I can't just rewrite anything I want. – soger Mar 06 '17 at 10:43
  • That makes no sense at all. You're obviously changing how you're calling the Perl script, so we're only talking about changing new code or code you're already changing. And even if the calls to `.ajax` are all over the place (which thye shouldn't be), it's trivial to apply `s/data: \K(\w+)/JSON.stringify($1)/g` to your files. – ikegami Mar 06 '17 at 18:06
1

PHP interprets a query string like foo[]=123&foo[]=234&foo[]=345 differently than all of the Perl web frameworks. I once built a PHP plugin for Mojolicious and had to work out a lot of those differences. You can find a summary of those differences and some working code (working for up to 2-level hashes, anyway) to convert Perl params to PHP-style params here. Perhaps you could adapt it for your use case.

mob
  • 117,087
  • 18
  • 149
  • 283