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.

8
  • JSON, JSON::XS... Commented Mar 1, 2017 at 17:58
  • 1
    @choroba That's not JSON. Commented Mar 1, 2017 at 17:58
  • I know but isn't it possible to switch to JSON instead? Commented Mar 1, 2017 at 18:01
  • 1
    Do you control the JavaScript code? If so, convert your object to a JSON string, URL encode it, and send that. JSON is a language-independent standard, while jQuery's .param method is not. Also see stackoverflow.com/q/15872658 Commented Mar 1, 2017 at 18:10
  • 2
    @ThisSuitIsBlackNot Better yet, skip all the URI encoding, query parsing, query param size limits, and security issues of sending data via GET and POST JSON. Commented Mar 1, 2017 at 18:15

2 Answers 2

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.
Sign up to request clarification or add additional context in comments.

12 Comments

Consider turning that into a module?
There's no spec, but the implementation is pretty short. Of course, it's changed several times over the years...
@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?
@ikegamy also shouldn't the line for (my $k_ = $k) be: for (my $_ = $k) I think that's just a typo.
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.
|
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.