soger
soger

Reputation: 1237

How can I parse complex query strings generated by jQuery.ajax() in Perl?

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.

Upvotes: 2

Views: 330

Answers (2)

ikegami
ikegami

Reputation: 385655

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.

Upvotes: 4

mob
mob

Reputation: 118605

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.

Upvotes: 1

Related Questions