http://qs321.pair.com?node_id=1154747

dorko has asked for the wisdom of the Perl Monks concerning the following question:

Hello,

I'm looking to turn a Perl data structure into a CGI query string.

There are several modules that turn query strings into data structures (eg CGI::Struct and CGI::State), but I need to do the opposite.

I've got something like this:

%hash = ( 'name' => 'test name', 'file_ids' => [ 1, 2 ], 'sub' => { 'name' => 'foo', 'message' => 'bar' }, );

And I want to turn it into something like this:

name=test+name&file_ids[]=1&file_ids[]=2&sub[name]=foo&sub[message]=bar

Even "data structure ==> JSON ==> query string" would be ok too.

I'm not finding any modules that do what I'm looking for. Does something like this exist?

Cheers,

Brent

-- Yeah, I'm a Delt.

Replies are listed 'Best First'.
Re: Building a URL Query String from a Data Structure
by Your Mother (Archbishop) on Feb 09, 2016 at 19:15 UTC

    Caveats apply. This is not recursive and it almost certainly should be; it fails on deeper data structures. It only serializes. I am almost positive there is something in some corner of the CPAN that does this but I couldn't find it in a somewhat casual search. This is first draft quality code. This style of query string is popular with PHP and Ruby apps. I think this is how jQuery does it if you don't request "traditional" (meaning *standards compliant* serialization) so it might be good to just copy its code if there turns out to be nothing solid in Perl already.

    use strictures; use Carp; use URI::Escape; my %hash = ( "name" => "test name", "file_ids" => [ 1, 2 ], "sub" => { "name" => "foo", "message" => "bar" } ); print serialize_web2_0_query_string(\%hash), $/; sub serialize_web2_0_query_string { no warnings "uninitialized"; my $datum = shift; my $strings = shift || []; for my $key ( keys %$datum ) { if ( ref $datum->{$key} eq "HASH" ) { while ( my ( $k, $v ) = each %{ $datum->{$key} } ) { push @$strings, sprintf "%s[%s]=%s", uri_escape($key), + uri_escape($k), uri_escape($v); } } elsif ( ref $datum->{$key} eq "ARRAY" ) { push @$strings, sprintf "%s[]=%s", uri_escape($key), uri_e +scape($_) for @{ $datum->{$key} }; } elsif ( not ref $datum->{$key} ) { push @$strings, join "=", map uri_escape($_), $key, $datum +->{$key}; } else { croak "Nope. Don't know what to do with ", $key, " => ", $ +datum->{$key}; } } return join "&", @$strings } __DATA__ sub[name]=foo&sub[message]=bar&file_ids[]=1&file_ids[]=2&name=test%20n +ame
      That works for me. You're the winner, Your Mother. I've made a couple of minor edits and included an attribution to you and a link to this thread in my comments.

      Thank you for your time and assistance. It's greatly appreciated.

      Cheers,

      Brent

      -- Yeah, I'm a Delt.
Re: Building a URL Query String from a Data Structure
by neilwatson (Priest) on Feb 09, 2016 at 17:49 UTC
      Thank you taking the time to help out a poor lost soul.

      I got it installed and working just before I left work yesterday. (CPAN got stuck in a seemingly infinite loop working out dependencies, so I just started installing the dependencies until CPAN could get HTTP::Request::JSON installed.)

      This code:

      #!/usr/bin/perl use strict; use warnings; use feature("say"); use Data::Dumper::Simple; use HTTP::Request::JSON; my $perl_structure = { 'sub' => { 'name' => 'foo', 'message' => 'bar' }, 'name' => 'test name', 'file_ids' => [ 1, 2 ] }; my $request = HTTP::Request::JSON->new; $request->json_content($perl_structure); say $request->decoded_content; say Dumper $request;

      results in this output:

      {"file_ids":[1,2],"name":"test name","sub":{"message":"bar","name":"fo +o"}} $request = bless( { '_content' => '{"file_ids":[1,2],"name":"test name +","sub":{"message":"bar","name":"foo"}}', '_uri' => undef, '_headers' => bless( { 'content-type' => 'applicat +ion/json', 'accept' => 'application/js +on' }, 'HTTP::Headers' ), '_method' => undef }, 'HTTP::Request::JSON' );

      So it looks like HTTP::Request::JSON creates an HTTP::Request object set up for a POST that contains a JSON payload.

      That gets me two thirds the way there, but I'm missing the CGI query string. I'm working with a RESTful web API. I need to send a GET and the API only accepts the query string. It ignores any content body that is sent. Actually, that's why I'm doing this. The API used to work fine with JSON sent as an attachment, then somebody got all standards compliant (or something) and now their GET requests only work with query strings passing data.

      Cheers,

      Brent

      -- Yeah, I'm a Delt.
Re: Building a URL Query String from a Data Structure
by thomas895 (Deacon) on Feb 09, 2016 at 18:58 UTC

    Even "data structure ==> JSON ==> query string" would be ok too.
    So just use JSON and URI::Escape?

    -Thomas
    "Excuse me for butting in, but I'm interrupt-driven..."
      Hi thomas895,

      If I understand you correctly, you're suggesting something like this:

      use strict; use warnings; use JSON; use URI::Escape; my $json_string = JSON::to_json($perl_structure); print URI::Escape::uri_escape($json_string);

      Output:

      %7B%22sub%22%3A%7B%22name%22%3A%22foo%22%2C%22message%22%3A%22bar%22%7D%2C%22file_ids%22%3A%5B1%2C2%5D%2C%22name%22%3A%22test%20name%22%7D

      So that's kind of like saying:

      https://example.com/api/v1/dosomething?{"file_ids":[1,2],"name":"test name","sub":{"message":"bar","name":"foo"}}

      and I need to be saying:

      https://example.com/api/v1/dosomething?name=test+name&file_ids[]=1&fil +e_ids[]=2&sub[name]=foo&sub[message]=bar

      I did take a look at the URI module and I didn't see anything there...

      Cheers,

      Brent

      -- Yeah, I'm a Delt.

        You said JSON would be fine, so that's what I suggested. How else would you do the "data structure ==> JSON ==> query string" thing?

        -Thomas
        "Excuse me for butting in, but I'm interrupt-driven..."