Adding an array into a JSON struct in Perl - 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'
# }
# ]
# };

Related

Perl hash add values

I am trying to push values into hash. I want to add the values under 'par3'.
e.g.
$VAR1 = { 'obj1' => ['par1',
'par2',
'par3' => ['par4','par5','par6',....]]}
I should also be able to add elements into 'par3' in case 'obj1'-'par1'-'par2'-'par3' matches.
So far I have this, but I can't figure out how can I add "the second level" under 'par3':
push #{$a{$obj}},$par1,$par2,$par3
[ ... ] is a reference to an array. Array elements are scalars. So it is not possible to directly have the structure you seem to be requesting (ie. the par3 => [ ... ] pseudocode from your question). See perldsc
It's not obvious what you are trying to do but a couple of possible ideas might be to use a reference to a hash, or to replace the array with a hash:
use Data::Dumper;
$Var2a = {
'obj1' => [
'par1',
'par2',
{ 'par3' => undef, }
],
};
push #{ $Var2a->{obj1}[2]{par3} }, 'par4', 'par5', 'par6';
print Dumper $Var2a;
$Var2b = {
'obj1' => {
'par1' => undef,
'par2' => undef,
'par3' => undef,
},
};
push #{ $Var2b->{obj1}{par3} }, 'par4', 'par5', 'par6';
print Dumper $Var2b;
I think you want to add children to par3. As such, I think you want pars to have children. If so, you need a different data structure.
# Ordered
my $obj1 = [
{ name => 'par1', children => [ ] },
{ name => 'par2', children => [ ] },
{ name => 'par3', children => [
{ name => 'par4', children => [ ] },
{ name => 'par5', children => [ ] },
{ name => 'par6', children => [ ] },
] },
];
or
# Unordered
my $obj1 = {
par1 => { },
par2 => { },
par3 => {
par4 => { },
par5 => { },
par6 => { },
},
};
To append par7 to par3's children, you would use
# Ordered
use List::Util qw( first );
my $par7 = { name => 'par7', children => [ ] };
my $par3 = first { $_->{ name } eq 'par3' } #$obj1
or die( "par3 not found" );
push #{ $par3->{ children } }, $par7;
or
# Unordered
$obj1->{ par3 }{ par7 } = { };

Accessing xml data using perl XML::Simple

I have some xml data, the dump looks like this:
$VAR1 = {
'Members' => [
{
'Age' => '19',
'Name' => 'Bob'
},
{
'Age' => '18',
'Name' => 'Jane'
},
{
'Age' => '21',
'Name' => 'Pat'
},
{
'Age' => '22',
'Name' => 'June'
}
],
'Sports' => [
{
'Players' => '20',
'Name' => 'Tennis'
},
{
'Players' => '35',
'Name' => 'Basketball'
}
],
};
I have tried the following code to print out the data:
foreach my $member (#($xml->{Members})) {
print("Age: $xml->{Age}");
}
But keep getting errors like:
Can't use string ("4") as a HASH ref while "strict refs" in use
Any idea why this won't work?
You are using the wrong syntax.
# here ... and here
# V V
foreach my $member (#($xml->{Members})) { ... }
To dereference, you need curly braces {}, not parenthesis ().
Once you've fixed that (which I think was a typo in the question, not in your real code), you have:
foreach my $member ( #{ $xml->{Members} } ) {
print "Age: $xml->{Age}";
}
But that's still wrong. You want to access the $member, not the whole $xml structure, because that doesn't have an Age, does it?
foreach my $member ( #{ $xml->{Members} } ) {
print "Age: $member->{Age}\n";
}
That will give you
Age: 19
Age: 18
Age: 21
Age: 22

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);

dynamically create url from hash ref in 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)));

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.