Search code examples
regexloopsperl

Perl loop within regex match


Take a look at this command:

perl -0777ne 'print "$&\n\n" while /"(QueryString|Params)":\[(\{"Name":".*?", ?"Value":".*?"\},? ?)*\]/g;' myfile.json

It iterate through each match of json strings like:

{
    "Some": "Random stuff",
    "QueryString": [
       { "Name": "IsOrdered",    "Value": "1"              },
       { "Name": "TimeStamp",    "Value": "11654116426247" }
    ],
    "Params": [
       { "Name": "ClassName",    "Value": "PRODUCT"        },
       { "Name": "ListID",       "Value": "Products"       },
       { "Name": "Mode ",        "Value": "1"              },
       { "Name": "Dept"  ,       "Value": "5"              },
       { "Name": "HasPrevOrder", "Value": ""               }
    ],
    "And": {
        "QueryString":[]
    },
    "More": "like",
    "More+": "this"
}

Now my question is how to iterate through each regex match of the Name/Value pairs, and join them together back to normal http query string?

For .e.g, For

"QueryString":[{"Name":"IsOrdered", "Value":"1"}, {"Name":"TimeStamp", "Value":"11654116426247"}]

the joined output should be

"QueryString":"IsOrdered=1&TimeStamp=11654116363378"

and "QueryString":[] to "QueryString":""

Note that I want to do regex match & replace because I need the rest of the JSON components be preserved. The JSON file I'm talking about is actually a har file. It's quit a complicated structure, yet

"(QueryString|Params)":\[(\{"Name":".*?", ?"Value":".*?"\},? ?)*\]

is all that I want to replace. Nothing more.


Solution

  • I'd use jq.

    jq '
       walk(
          if type == "object" then
             (
                ( .QueryString, .Params ) | select( . != null )
             ) |= (
                map( @uri "\( .Name )=\( .Value )" ) | join("&")
             )
          else
             .
          end
       )
    '
    

    Demo on jqplay

    This modifies all object with elements with one of those keys. I usually prefer something more targeted (not just for efficiency reasons, but to avoid accidentally changing something that shouldn't be changed), but I don't have enough knowledge of the HAR format to do this.


    The following is a Perl program that would also achieve the task:

    use feature qw( say );
    
    use Cpanel::JSON::XS qw( decode_json encode_json );
    use URI::Escape      qw( uri_escape_utf8 );
    
    sub transform {
       for ( @_ ) {
          $_ =
             join "&",
                map {
                   join "=",
                      map uri_escape_utf8( $_ ),
                         $_->@{qw( Name Value )}
                }
                   @$_;
       }
    }
    
    sub fix {
       my $x = shift;
       my $type = ref( $x );
       if ( $type eq "HASH" ) {
          for my $k ( keys( %$x ) ) {
             for my $v ( $x->{ $k } ) {
                if ( $k eq "QueryString" || $k eq "Params" ) {
                   transform( $v );
                } else {
                   fix( $v );
                }
             }
          }
       }
       elsif ( $type eq "ARRAY" ) {
          fix( $_ ) for @$x;
       }
    }
    
    local $/;
    while ( <> ) {
       my $data = decode_json( $_ );
       fix( $data );
       say( encode_json( $data ) );
    }