dynamically create url from hash ref in perl - perl

I want to create url from input hash ref. Suppose I give hash ref as
my $input_hash_ref = {
'1' => 'A',
'2' => 'B',
'3' => {
'4' => {
'5' => {
'6' => [
'ice cream','drink'
],
'7' => 'large'
}
'8' => 'perl'
'9' => 'rosy'
},
'10'=>'june'
},
};
Then this is to be converted as
1=A&2=B&3.4.5.6=ice cream|drinks&3.4.5.7=large&3.8=perl&3.9=rosy&10=june
Help needed.

I just have to say, you shouldn't be composing query strings with a naive implementation with join.
use URI;
use URI::QueryParam;
my $u = URI->new("","http");
Then you can simply:
$u->query_param_append("1" => "A", "2" => "B", ....);
Or even
$u->query_form_hash( %somedata );
Note that this does not automatically deal with your custom schema for serialized nesting, but it does guarantee that you'll emit a valid query string that any server will understand.
Though you can also use a Perl Module to convert from a deeply-nested Hash to a Flat hash and back again:
Data::SplitSerializer
And you can use this to convert between formats on both sides.
Example usage:
use strict;
use warnings;
use utf8;
use Data::SplitSerializer;
use Data::Dump qw(pp);
use URI;
use URI::QueryParam;
my $input_hash = {
'1' => 'A',
'2' => 'B',
'3' => {
'4' => {
'5' => {
'6' => [
'ice cream','drink'
],
'7' => 'large'
}
},
},
'8' => 'june',
'9' => "Challenging & Value",
};
my $flattened = Data::SplitSerializer->new( path_style => 'DZIL' )->serialize($input_hash);
pp $flattened;
my $uri = URI->new("http://example.com/thing?");
$uri->query_form_hash( $flattened );
printf "%s\n", $uri;
my $copy = URI->new( $uri . "" ); # simulate getting it server side
my $copy_hash = $copy->query_form_hash;
pp $copy_hash;
my $deep = Data::
SplitSerializer->new( path_style => 'DZIL' )->deserialize($copy_hash);
pp $deep;
Example Output:
{
"1" => "A",
"2" => "B",
"3.4.5.6[0]" => "ice cream",
"3.4.5.6[1]" => "drink",
"3.4.5.7" => "large",
"8" => "june",
"9" => "Challenging & Value",
}
http://example.com/thing?9=Challenging+%26+Value&3.4.5.6%5B1%5D=drink&2=B&8=june&3.4.5.6%5B0%5D=ice+cream&1=A&3.4.5.7=large
{
"1" => "A",
"2" => "B",
"3.4.5.6[0]" => "ice cream",
"3.4.5.6[1]" => "drink",
"3.4.5.7" => "large",
"8" => "june",
"9" => "Challenging & Value",
}
{
1 => "A",
2 => "B",
3 => { 4 => { 5 => { 6 => ["ice cream", "drink"], 7 => "large" } } },
8 => "june",
9 => "Challenging & Value",
}

use URI::Escape;
sub serial {
my ($h, $p) = #_;
return join "&", map {
my $v = $h->{$_};
my $ref = ref($v);
my $isH = $ref eq "HASH";
my $pp = join ".", grep defined, $p, $_;
$v = $isH ? serial($v,$pp)
: $ref ? join("|", map uri_escape($_), #$v)
: uri_escape($v);
$isH ? $v : "$pp=$v";
}
sort keys %$h;
}
my $input_hash_ref = {
'1' => 'A',
'2' => 'B',
'3' => {
'4' => {
'5' => {
'6' => [
'ice cream','drink'
],
'7' => 'large'
}
},
},
'8' => 'june'
};
print serial($input_hash_ref);
output
1=A&2=B&3.4.5.6=ice cream|drink&3.4.5.7=large&8=june

sub c {
my ($v, $p) = #_;
my $r = ref($v);
return map { c($v->{$_}, $p ? $p . '.' . $_ : $_) } keys(%$v) if $r eq 'HASH';
return $p . '=' . join('|', #$v) if $r eq 'ARRAY';
return $p . '=' . $v;
}
say(join('&', c($input_hash_ref)));

Related

Merging of duplicate path values of a Perl HOH

I have a perl HOH (hash of hash) wherein i have serial numbers 1,2,3.... so on and directory paths with associated counter values. Could anyone please suggest a way in which i can merge the duplicate internal paths (e.g, /usr/lib , /bin/ ) to a form a new hash with addition of the values?
Sample HOH:
$VAR1 = {
'1' => {
'/usr/lib' => 18
},
'3' => {
'/bin/' => '3'
},
'4' => {
'/usr/lib' => 12
},
'2' => {
'/bin/' => '6'
},
'5' => {
'/dev/' => '2'
},
'6' => {
'/tmp/' => '8'
}
};
Final output requirement i am looking for is a simple hash with combined values. No need of serial numbers:
$VAR1 = {
'/usr/lib' => '30',
'/bin/' => '9',
'/dev/' => '2',
'/tmp/' => '8'
};
Try
sub aggregate_counts {
my $HOH = shift;
my %out;
for my $h (values %$HOH) {
for my $k ( keys %$h) {
$out{$k} += $h->{$k};
}
}
\%out
}
Use this subroutine like aggregate_counts($hoh) to get hash reference in required format.
Complete Script for testing:
use strict;
use warnings;
sub aggregate_counts {
my $HOH = shift;
my %out;
for my $h (values %$HOH) {
for my $k ( keys %$h) {
$out{$k} += $h->{$k};
}
}
\%out
}
my
$VAR1 = {
'1' => {
'/usr/lib' => 18
},
'3' => {
'/bin/' => '3'
},
'4' => {
'/usr/lib' => 12
},
'2' => {
'/bin/' => '6'
},
'5' => {
'/dev/' => '2'
},
'6' => {
'/tmp/' => '8'
}
};
use Data::Dumper;
print Dumper(aggregate_counts($VAR1))
Output:
$VAR1 = {
'/dev/' => '2',
'/tmp/' => '8',
'/bin/' => 9,
'/usr/lib' => 30
};
This could help you:
use strict; use warnings;
use Data::Dumper;
my %hash = (
'1' => {
'/usr/lib' => 18
},
'3' => {
'/bin/' => '3'
},
'4' => {
'/usr/lib' => 12
},
'2' => {
'/bin/' => '6'
},
'5' => {
'/dev/' => '2'
},
'6' => {
'/tmp/' => '8'
}
);
my %result;
foreach my $key (keys %hash){
foreach my $inner (keys %{$hash{$key}}) {
$result{$inner} += $hash{$key}{$inner};
}
}
print Dumper(\%result);
Output:
$VAR1 = {
'/usr/lib' => 30,
'/bin/' => 9,
'/dev/' => 2,
'/tmp/' => 8
};
Note: Please post your code as well in the question while asking for a help in SO.
You don't care about the keys of the outer hash. So let's start with values(%$VAR1):
{ '/usr/lib' => 18 },
{ '/bin/' => '3' },
{ '/usr/lib' => 12 },
{ '/bin/' => '6' },
{ '/dev/' => '2' },
{ '/tmp/' => '8' },
Hashes are great for grouping. We're going to iterate over the above list, then we're going to iterates over the elements of each of those hashes, using a hash to group them.
my %grouped;
for my $inner (values(%$VAR1)) {
for my $key (keys(%$inner)) {
my $val = $inner->{$key};
$grouped{$key} += $val;
}
}
And we're already done.

How do I merge hashes from an array of hashes

Input array:
my #input = (
{
Id => 1,
A => "abcd",
B => undef,
C => "rtyt"
},
{
Id => 1,
A => undef,
B => "efgh",
},
{
Id => 2,
A => "ifk",
B => "rjot",
},
);
Desired output:
my #output = (
{
Id => 1,
A => "abcd",
B => "efgh",
C => "rtyt"
},
{
Id => 2,
A => "ifk",
B => "rjot",
},
);
Other solutions I've seen here either include the extra values from first hash or from second hash, but I need both.
Hashes are great for grouping things.
# Keep first value in case of conflict.
my %by_id;
for my $rec (#input) {
my $id = $rec->{Id};
for my $k (keys(%$rec)) {
$by_id{$id}{$k} //= $rec->{$k};
}
}
or
# Keep last value in case of conflict.
my %by_id;
for my $rec (#input) {
my $id = $rec->{Id};
$by_id{$id} //= { %$rec };
for my $k (keys(%$rec)) {
if (defined($rec->{$k})) {
$by_id{$id}{$k} = $rec->{$k};
}
}
}
The above builds
my %by_id = (
"1" => {
Id => 1,
A => "abcd",
B => "efgh",
C => "rtyt",
},
"2" => {
Id => 2,
A => "ifk",
B => "rjot",
},
);
Then, we can simply extract the values.
my #output = #by_id{
sort { $a <=> $b }
keys(%by_id)
};

Adding an array into a JSON struct in Perl

I have a hash which looks as following (In Perl):
'multi' => {
'key2' => '123',
'key1' => 'abc',
'key3' => 'xwz'
}
Also I have the following structure:
my $json_struct = {
name => $name,
time => $time,
};
I would like to insert multi into the $json_struct as a array with keys and values.
I use the $json_struct in order to convert it to a JSON file, so the JSON output should look as following:
{
"name" : "some_name",
"time" : "time",
"multi" : [
{
"key" : "key1",
"value" : "abc"
},
{
"key" : "key2",
"value" : "123"
},
{
"key" : "key3",
"value" : "xwz"
}
],
}
How can I do it? What is the cleanest way possible (without importing additional modules).
To add an array, you just add the reference to the array into your data structure:
$json_struct->{multi} = \#multi;
In the case that %multi is a hash, you can use:
my #multi = map { +{ key => $_, value => $multi{ $_ } } } sort keys %multi;
$json_struct->{multi} = \#multi;
See Also
https://perldoc.perl.org/perlreftut.html
#!/usr/bin/env perl
use v5.10.1;
use warnings FATAL => "all";
use autodie;
use Data::Dumper;
my $payload = {
'multi' => {
'key2' => '123',
'key1' => 'abc',
'key3' => 'xwz'
}
};
my $json_struct = {
name => "foo",
time => "2018-12-03",
};
for my $key (keys %$payload) {
my #as_array = map {
{
key => $_,
value => $payload->{$key}{$_}
}
} keys %{$payload->{$key}};
$json_struct->{$key} = \#as_array;
}
say Dumper $json_struct;
# $VAR1 = {
# 'name' => 'foo',
# 'time' => '2018-12-03',
# 'multi' => [
# {
# 'value' => 'abc',
# 'key' => 'key1'
# },
# {
# 'value' => '123',
# 'key' => 'key2'
# },
# {
# 'key' => 'key3',
# 'value' => 'xwz'
# }
# ]
# };

Convert a flat datastructure into a tree

I have an array of hashes. Each element in the array is a node in a hierarchical tree and has referential data for who the parent is. I will have thousands and hundreds of thousands of nodes in the tree... essentially an unknown set of nodes has to be converted to JSON (shown below) for use with http://bl.ocks.org/robschmuecker/7880033
UPDATE: position_id is a node in the heretical tree. placement_id is the parent's position_id (adjacency referential tree).
UPDATE: Here's the full AoH Data::Dumper result with Nested Set and Adjacency result from a modified version of DBIx::Tree::NestedSet (custom).
$VAR1 = [
{
'lft' => '673',
'id' => '109',
'date_created' => '2015-08-15',
'level' => '7',
'user_id' => '13',
'placement_id' => '11',
'position_id' => '13',
'status' => '1',
'structure_id' => '1',
'rght' => '684'
},
{
'placement_id' => '13',
'position_id' => '22',
'status' => '1',
'structure_id' => '1',
'rght' => '679',
'lft' => '674',
'date_created' => '2015-08-15',
'id' => '116',
'level' => '8',
'user_id' => '22'
},
{
'user_id' => '101',
'level' => '9',
'id' => '200',
'date_created' => '2015-08-15',
'lft' => '675',
'rght' => '676',
'structure_id' => '1',
'status' => '1',
'position_id' => '101',
'placement_id' => '22'
},
{
'date_created' => '2015-08-15',
'id' => '201',
'level' => '9',
'user_id' => '374',
'lft' => '677',
'structure_id' => '1',
'rght' => '678',
'placement_id' => '22',
'position_id' => '374',
'status' => '1'
},
{
'lft' => '680',
'user_id' => '95',
'level' => '8',
'id' => '117',
'date_created' => '2015-08-15',
'status' => '1',
'position_id' => '95',
'placement_id' => '13',
'rght' => '681',
'structure_id' => '1'
}
];
THIS IS THE GOAL, For this example I need to end up with:
{
"name": "13",
"children": [
{
"name": "22",
"children": [
{
"name": "101"
},
{
"name": "374"
}
]
},
{
"name": "95"
}
]
}
You can also see the format I am trying to arrive at here (minus size):
http://bl.ocks.org/robschmuecker/7880033#flare.json
My failed approach(es) included various attempts at looping through the array of hashes to create a recursive Hash of Hashes that can then be used with the JSON Perl module to create the actual JSON I need.
my $data = [
{ position_id => 123, placement_id => undef },
{ position_id => 456, placement_id => 123 },
{ position_id => 789, placement_id => 123 },
# ...
];
my $roots;
{
my %recs_by_name;
my %children_by_parent_name;
for my $row (#$data) {
my $name = $row->{position_id};
my $parent_name = $row->{placement_id};
my $rec = {
name => $name,
};
push #{ $children_by_parent_name{$parent_name // 'root'} }, $rec;
$recs_by_name{$name} = $rec;
}
$roots = delete($children_by_parent_name{root}) || [];
for my $name (keys(%children_by_parent_name)) {
my $children = $children_by_parent_name{$name};
if ( my $rec = $recs_by_name{$name} ) {
$rec->{children} = $children;
} else {
die("Parent $name doesn't exist.\n");
push #$roots, #$children;
}
}
}
print(Dumper($roots));
Tested.
You appear to have the depth of each node available to you (level). Simpler code could be used if your data was sorted by increasing depths.
While it was #ikegami who ultimately answered the question that led to the solution. I believe the following adaptation adds 4 important elements/clarifications I found helpful, and thought others reading this question and answer would also find useful.
1- Clear addition of all key,value pairs from the originating AoH to the resulting HOH. See while loop.
2- A Child node counter.
3- Inclusion and use of the encode_json function from JSON
4- The result is also an Array with a Hash as the first element. Newbies (like me) might find the explicit #{$roots}[0] passed to encode_json as helpful.
At first I had a similar adapted solution posted as an UPDATE within my question, but was admonished that it was bad etiquette and instructed to post an answer.
#ikegami's deserves the credit for the core of the solution.
sub get_jsonTree {
my ($array_of_hashes_ref) = #_;
my $roots;
my %recs_by_name;
my %children_by_parent_name;
my %count;
for my $row (#$array_of_hashes_ref) {
my $name = $row->{position_id};
my $parent_name = $row->{placement_id};
my $rec = {
name => $name,
};
## Added to loop through all key,value pairs and add them to $rec
while ( my ($key, $value) = each(%$row) ) {
$rec->{$key} = $value;
}
##Added To Count Child Nodes
$count{$parent_name} = 0 if (!$count{$parent_name});
$rec->{'child_count'} = $count{$parent_name};
$count{$parent_name}++;
push #{ $children_by_parent_name{$parent_name // 'root'} }, $rec;
$recs_by_name{$name} = $rec;
}
$roots = delete($children_by_parent_name{root}) || [];
for my $name (keys(%children_by_parent_name)) {
my $children = $children_by_parent_name{$name};
if ( my $rec = $recs_by_name{$name} ) {
$rec->{children} = $children;
} else {
$util{'test'} .= "Parent $name doesn't exist.\n<BR>";
push #$roots, #$children;
}
}
use JSON;
my $json_str = encode_json(#{$roots}[0]);
return $json_str;
}
my $array_of_hashes_ref = [
{ position_id => 123, placement_id => undef },
{ position_id => 456, placement_id => 123 },
{ position_id => 789, placement_id => 123 },
# ...
];
my $json_str = &get_jsonTree($array_of_hashes_ref);

Search in LDAP with conditions?

When I do
#!/usr/bin/perl -w
use strict;
use Net::LDAP;
use Data::Dumper;
my $dn="...";
my $password="xxx";
my $ldap = Net::LDAP->new('example.com') or die "$#";
my $mesg = $ldap->bind($dn, password => $password);
if ($mesg->code) { die "uuuu $mesg"; }
$mesg = $ldap->search(
base => "dc=example,dc=com",
filter => "(name=LIST)",
);
print Dumper $mesg;
I get
$VAR1 = bless( {
'parent' => bless( {
...
}, 'Net::LDAP' ),
'entries' => [
bless( {
'changes' => [],
'changetype' => 'modify',
'asn' => {
'objectName' => 'CN=LIST,OU=test group,OU=M,OU=I,DC=example,DC=com',
'attributes' => [
{
'type' => 'objectClass',
'vals' => [
'top',
'group'
]
},
{
'type' => 'cn',
'vals' => [
'LIST'
]
},
{
'type' => 'member',
'vals' => [
'CN=user1,OU=BaseUsers,DC=example,DC=com',
'CN=user2,OU=BaseUsers,DC=example,DC=com',
]
},
...
where I would only like to output those from member that have in their object
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=example,DC=com
Does anyone know how to do that?
foreach my $entry (#{$mesg->{'entries'}})
{
my $match = 0;
my $name = $entry->{'asn'}->{'objectName'};
foreach my $attr (#{$entry->{'asn'}->{'attributes'}})
{
if('member' eq $attr->{'type'})
{
foreach my $val (#{$attr->{'vals'}})
{
if($val =~ /^CN=.*,CN=.*,CN=.*,DC=example,DC=com$/)
{
$match = 1;
last;
}
}
}
}
if($match)
{
print $name;
}
}
For your example data above this will return no matches since none of the "members" match the search pattern you specified. Also I wasn't sure if you wanted to output the object name (as per my code) or the matching string. If the latter you don't need $match, simply put a print in the innermost block.