I am trying to merge two yml files with following perl code. But data is not writing in expected format.
Code:-
use warnings;
use strict;
use feature 'say';
use Data::Dump;
use Data::Dumper;
use Storable qw(dclone);
use YAML qw(LoadFile Dump);
use File::Slurp qw(write_file);
use Hash::Merge qw(merge);
Hash::Merge::set_behavior('RETAINMENT_PRECEDENT');
die "Usage: $0 file1 file2\n" if #ARGV != 2;
my ($fname1, $fname2) = #ARGV;
my $yml1 = LoadFile($fname1);
my $yml2 = LoadFile($fname2);
my $res = merge($yml1, $yml2);
#print Dumper $res;
write_file("final.yml", Dump ($res));
Should we need to set any custom settings in perl Hash::Merge module?
Hash Merge change:-
Hash::Merge::specify_behavior(
{ SCALAR => {
SCALAR => sub { $_[1] },
ARRAY => sub { [ $_[0], #{ $_[1] } ] },
HASH => sub { $_[1] },
},
ARRAY => {
SCALAR => sub { $_[1] },
HASH => sub { $_[1] },
ARRAY => sub { # Rule for 'local_pointer'
if ( exists $_[0][0]->{data} && exists $_[1][0]->{data} && exists $_[0][0]->{name} && exists $_[1][0]->{name}){
#if ($_[0][0]->{name} eq $_[1][0]->{name}){
my #a1 = #{$_[0]};
my #a2 = #{$_[1]};
foreach my $i1 (0..$#a1) {
foreach my $i2 (0..$#a2) {
#if ($a1[$i1]->{name} eq $a2[$i2]->{name}){
#print Dumper $a1[0]->{data};
[ { data => [
$a1[$i1]->{data},
$a2[$i2]->{data},
],
name => $a2[$i2]->{name},
}
]
#else{
#}
#}
}
}
}
else {
[ #{ $_[0] }, #{ $_[1] } ] }
}
},
HASH => {
SCALAR => sub { $_[1] },
ARRAY => sub { [ values %{ $_[0] }, #{ $_[1] } ] },
HASH => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },
},
},
'M2',
);
You can do a simple merge on these yaml snippets without Hash::Merge, example:
use strict;
use warnings;
use feature qw[say];
use DDP;
use YAML qw[LoadFile];
my $yaml = {};
my #data;
foreach my $file ( './a.yml', 'b.yml' ) {
my $y = LoadFile($file);
push #{ $yaml->{src} }, #{ $y->{src} };
foreach my $element ( #{ $y->{local_pointer} } ) {
push #data, #{ $element->{data} };
}
}
#{ $yaml->{local_pointer} } = #data;
say p $yaml;
output:
\ {
local_pointer => [
[0] {
dir => '/release/p_analysis' (U),
exe => 'bin/test.sh' (U),
tool_name => 'power_hash' (U),
type => 'exe' (U)
},
[1] {
dir => '/complete/release/1.2/55603074' (U),
name => 'code_test.config' (U),
tool_name => 'axi_ip' (U),
type => 'dir' (U)
},
[2] {
dir => '/hw/debug/test/log/' (U),
exe => 'log.pl' (U),
tool_name => 'test1' (U),
type => 'exe' (U)
},
[3] {
dir => '/releaase_test/p_analysis' (U),
exe => 'bin/test1.sh' (U),
name => 'code_test.config' (U),
tool_name => 'power1_hash' (U),
type => 'exe' (U)
}
],
src => [
[0] {
dest => 'sw_config/' (U),
ip => 'TOT' (U),
name => 'defs.yml' (U)
},
[1] {
dest => 'models/' (U),
ip => 'TOT' (U),
name => 'makebuild.vh' (U)
},
[2] {
dest => 'sw_configs/' (U),
ip => 'TOT' (U),
name => 'defs111.yml' (U)
},
[3] {
dest => 'models/' (U),
ip => 'TOT' (U),
name => 'testbuild.vh' (U)
}
]
UPDATE
Here a Hash::Merge example with a specific rule for local_pointer as in the previous example:
my #yaml = map { LoadFile( sprintf './%s.yml', $_ ) } qw[a b];
my $merger = Hash::Merge->new();
$merger->add_behavior_spec(
{ SCALAR => {
SCALAR => sub { $_[1] },
ARRAY => sub { [ $_[0], #{ $_[1] } ] },
HASH => sub { $_[1] },
},
ARRAY => {
SCALAR => sub { $_[1] },
HASH => sub { $_[1] },
ARRAY => sub { # Rule for 'local_pointer'
if ( exists $_[0][0]->{data} && exists $_[1][0]->{data} ) {
# return joined arrays into 'data'
[ { data => [
#{ $_[0][0]->{data} }, #
#{ $_[1][0]->{data} }, #
],
}
]
}
else { [ #{ $_[0] }, #{ $_[1] } ] }
}
},
HASH => {
SCALAR => sub { $_[1] },
ARRAY => sub { [ values %{ $_[0] }, #{ $_[1] } ] },
HASH => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },
},
},
'My Behavior',
);
my $yaml = $merger->merge(#yaml);
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'
# }
# ]
# };
In Perl, how do I get this:
$VAR1 = { '999' => { '998' => [ '908', '906', '0', '998', '907' ] } };
$VAR1 = { '999' => { '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] } };
$VAR1 = { '999' => { '996' => [] } };
$VAR1 = { '999' => { '995' => [] } };
$VAR1 = { '999' => { '994' => [] } };
$VAR1 = { '999' => { '993' => [] } };
$VAR1 = { '999' => { '997' => [ '986', '987', '990', '984', '989', '988' ] } };
$VAR1 = { '995' => { '101' => [] } };
$VAR1 = { '995' => { '102' => [] } };
$VAR1 = { '995' => { '103' => [] } };
$VAR1 = { '995' => { '104' => [] } };
$VAR1 = { '995' => { '105' => [] } };
$VAR1 = { '995' => { '106' => [] } };
$VAR1 = { '995' => { '107' => [] } };
$VAR1 = { '994' => { '910' => [] } };
$VAR1 = { '993' => { '909' => [] } };
$VAR1 = { '993' => { '904' => [] } };
$VAR1 = { '994' => { '985' => [] } };
$VAR1 = { '994' => { '983' => [] } };
$VAR1 = { '993' => { '902' => [] } };
$VAR1 = { '999' => { '992' => [ '905' ] } };
to this:
$VAR1 = { '999:' => [
{ '992' => [ '905' ] },
{ '993' => [
{ '909' => [] },
{ '904' => [] },
{ '902' => [] }
] },
{ '994' => [
{ '910' => [] },
{ '985' => [] },
{ '983' => [] }
] },
{ '995' => [
{ '101' => [] },
{ '102' => [] },
{ '103' => [] },
{ '104' => [] },
{ '105' => [] },
{ '106' => [] },
{ '107' => [] }
] },
{ '996' => [] },
{ '997' => [ '986', '987', '990', '984', '989', '988' ] },
{ '998' => [ '908', '906', '0', '998', '907' ] },
{ '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] }
]};
I think this is closer than anybody else has gotten:
This does most of what you want. I did not store things in arrays of singular
hashes, as I don't feel that that is useful.
Your scenario is not a regular one. I've tried to genericize this to some extent,
but was not possible to overcome the singularity of this code.
First of all because it appears you want to collapse everything with the same
id into a merged entity (with exceptions), you have to descend through the structure
pulling the definitions of the entities. Keeping track of levels, because you
want them in the form of a tree.
Next, you assemble the ID table, merging entities as possible. Note that you
had 995 defined as an empty array one place and as a level another. So given
your output, I wanted to overwrite the empty list with the hash.
After that, we need to move the root to the result structure, descending that in order
to assign canonical entities to the identifiers at each level.
Like I said, it's not anything that regular. Of course, if you still want a list
of hashes which are no more than pairs, that's an exercise left to you.
use strict;
use warnings;
# subroutine to identify all elements
sub descend_identify {
my ( $level, $hash_ref ) = #_;
# return an expanding list that gets populated as we desecend
return map {
my $item = $hash_ref->{$_};
$_ => ( $level, $item )
, ( ref( $item ) eq 'HASH' ? descend_identify( $level + 1, $item )
: ()
)
;
} keys %$hash_ref
;
}
# subroutine to refit all nested elements
sub descend_restore {
my ( $hash, $ident_hash ) = #_;
my #keys = keys %$hash;
#$hash{ #keys } = #$ident_hash{ #keys };
foreach my $h ( grep { ref() eq 'HASH' } values %$hash ) {
descend_restore( $h, $ident_hash );
}
return;
}
# merge hashes, descending down the hash structures.
sub merge_hashes {
my ( $dest_hash, $src_hash ) = #_;
foreach my $key ( keys %$src_hash ) {
if ( exists $dest_hash->{$key} ) {
my $ref = $dest_hash->{$key};
my $typ = ref( $ref );
if ( $typ eq 'HASH' ) {
merge_hashes( $ref, $src_hash->{$key} );
}
else {
push #$ref, $src_hash->{$key};
}
}
else {
$dest_hash->{$key} = $src_hash->{$key};
}
}
return;
}
my ( %levels, %ident_map, %result );
#descend through every level of hash in the list
# #hash_list is assumed to be whatever you Dumper-ed.
my #pairs = map { descend_identify( 0, $_ ); } #hash_list;
while ( #pairs ) {
my ( $key, $level, $ref ) = splice( #pairs, 0, 3 );
$levels{$key} |= $level;
# if we already have an identity for this key, merge the two
if ( exists $ident_map{$key} ) {
my $oref = $ident_map{$key};
my $otyp = ref( $oref );
if ( $otyp ne ref( $ref )) {
# empty arrays can be overwritten by hashrefs -- per 995
if ( $otyp eq 'ARRAY' && #$oref == 0 && ref( $ref ) eq 'HASH' ) {
$ident_map{$key} = $ref;
}
else {
die "Uncertain merge for '$key'!";
}
}
elsif ( $otyp eq 'HASH' ) {
merge_hashes( $oref, $ref );
}
else {
#$oref = sort { $a <=> $b || $a cmp $b } keys %{{ #$ref, #$oref }};
}
}
else {
$ident_map{$key} = $ref;
}
}
# Copy only the keys that do not appear at higher levels to the
# result hash
if ( my #keys = grep { !$levels{$_} } keys %ident_map ) {
#result{ #keys } = #ident_map{ #keys } if #keys;
}
# then step through the hash to make sure that the entries at
# all levels are equal to the identity
descend_restore( \%result, \%ident_map );
Use CPAN! Try Hash::Merge
# OO interface.
my $merge = Hash::Merge->new( 'LEFT_PRECEDENT' );
my %c = %{ $merge->merge( \%a, \%b ) };
See CPAN for more info, it pretty much does everything you would want to, and is fully customizable.
Give this recursive solution a try:
# XXX: doesn't handle circular problems...
sub deepmerge {
my (#structs) = #_;
my $new;
# filter out non-existant structs
#structs = grep {defined($_)} #structs;
my $ref = ref($structs[0]);
if (not all(map {ref($_) eq $ref} #structs)) {
warn("deepmerge: all structs are not $ref\n");
}
my #tomerge = grep {ref($_) eq $ref} #structs;
return qr/$tomerge[0]/ if scalar(#tomerge) == 1 and $ref eq 'Regexp';
return $tomerge[0] if scalar(#tomerge) == 1;
if ($ref eq '') {
$new = pop(#tomerge); # prefer farthest right
}
elsif ($ref eq 'Regexp') {
$new = qr/$tomerge[$#tomerge]/;
}
elsif ($ref eq 'ARRAY') {
$new = [];
for my $i (0 .. max(map {scalar(#$_) - 1} #tomerge)) {
$new->[$i] = deepmerge(map {$_->[$i]} #tomerge);
}
}
elsif ($ref eq 'HASH') {
$new = {};
for my $key (uniq(map {keys %$_} #tomerge)) {
$new->{$key} = deepmerge(map {$_->{$key}} #tomerge);
}
}
else {
# ignore all other structures...
$new = '';
}
return $new;
}
Modify it to your hearts content to achieve the desired result.
Upon further investigation, I noticed you're merging them in some different way than the above algorithm. Maybe just use this as an example then. Mine does this:
deepmerge({k => 'v'}, {k2 => 'v2'});
# returns {k => 'v', k2 => 'v2'}
And similar things for arrays.
I indented your wanted output as it was hard to read, for the benefit of other people who want to answer. I'm still thinking of an answer.
$VAR1 = { '999:' => [
{ '992' => [ '905' ] },
{ '993' => [
{ '909' => [] },
{ '904' => [] },
{ '902' => [] }
]
},
{ '994' => [
{ '910' => [] },
{ '985' => [] },
{ '983' => [] }
]
},
{ '995' => [
{ '101' => [] },
{ '102' => [] },
{ '103' => [] },
{ '104' => [] },
{ '105' => [] },
{ '106' => [] },
{ '107' => [] }
]
},
{ '996' => [] },
{ '997' => [ '986', '987', '990', '984', '989', '988' ] },
{ '998' => [ '908', '906', '0', '998', '907' ] },
{ '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] }
]
};
I don't see the point of all those single entry hashes though, would not the following be better?
$VAR1 = { '999:' => {
'992' => [ '905' ],
'993' => {
'909' => [],
'904' => [],
'902' => []
},
'994' => {
'910' => [],
'985' => [],
'983' => []
},
'995' => {
'101' => [],
'102' => [],
'103' => [],
'104' => [],
'105' => [],
'106' => [],
'107' => []
},
'996' => [],
'997' => [ '986', '987', '990', '984', '989', '988' ],
'998' => [ '908', '906', '0', '998', '907' ],
'991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ]
}
};
Assuming the above data is in a file dump.txt, you can eval it piece by piece.
Updated code below
use strict;
use File::Slurp;
my $final_data = {};
my #data = map {eval $_} (read_file("dump.txt") =~ /\$VAR1 = ([^;]+);/gs);
foreach my $element (#data) {
my $key = (keys %$element)[0];
$final_data->{$key} ||= [];
push #{$final_data->{$key}}, $element->{$key}
};
use Data::Dumper;
print Data::Dumper->Dump([$final_data]);
If you want to completely deep merge, you can at the end pass $final_data through this (not tested!!!) deep merger:
# Merge an array of hashes as follows:
# IN: [ { 1 => 11 }, { 1 => 12 },{ 2 => 22 } ]
# OUT: { 1 => [ 11, 12 ], 2 => [ 22 ] }
# This is recursive - if array [11,12] was an array of hashrefs, we merge those too
sub merge_hashes {
my $hashes = #_[0];
return $hashes unless ref $hashes eq ref []; # Hat tip to brian d foy
return $hashes unless grep { ref #_ eq ref {} } #$hashes; # Only merge array of hashes
my $final_hashref = {};
foreach my $element (#$hashes) {
foreach my $key (keys %$element) {
$final_hashref->{$key} ||= [];
push #{ $final_hashref->{$key} }, $element->{$key};
}
}
foreach my $key (keys %$final_hashref) {
$final_hashref->{$key} = merge_hashes($final_hashref->{$key});
}
return $final_hashref;
}
Use push and autovivification.
Start with the usual front matter:
#! /usr/bin/perl
use warnings;
use strict;
Read your sample input from the DATA filehandle and create a datastructure similar to the one you dumped:
my #hashes;
while (<DATA>) {
my $VAR1;
$VAR1 = eval $_;
die $# if $#;
push #hashes => $VAR1;
}
Your input has two cases:
A reference to an array that contains data to be merged with its cousins that have the same "key path."
Otherwise, it's a reference to a hash that contains a reference to an array from case 1 at some depth, so we strip off the outermost layer and keep digging.
Note the use of $_[0]. The semantics of Perl subroutines are such that the values in #_ are aliases rather than copies. This lets us call merge directly without having to first create a bunch of scaffolding to hold the merged contents. The code will break if you copy the value instead.
sub merge {
my $data = shift;
if (ref($data) eq "ARRAY") {
push #{ $_[0] } => #$data;
}
else {
foreach my $k (%$data) {
merge($data->{$k} => $_[0]{$k});
}
}
}
Now we walk #hashes and incrementally merge their contents into %merged.
my %merged;
foreach my $h (#hashes) {
foreach my $k (keys %$h) {
merge $h->{$k} => $merged{$k};
}
}
We don't know in what order the values arrived, so run a final cleanup pass to sort the arrays:
sub sort_arrays {
my($root) = #_;
if (ref($root) eq "ARRAY") {
#$root = sort { $a <=> $b } #$root;
}
else {
sort_arrays($root->{$_}) for keys %$root;
}
}
sort_arrays \%merged;
The Data::Dumper module is great for quick debugging!
use Data::Dumper;
$Data::Dumper::Indent = 1;
print Dumper \%merged;
Place a copy of the input from your question into the special DATA filehandle:
__DATA__
$VAR1 = { '999' => { '998' => [ '908', '906', '0', '998', '907' ] } };
$VAR1 = { '999' => { '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] } };
$VAR1 = { '999' => { '996' => [] } };
$VAR1 = { '999' => { '995' => [] } };
$VAR1 = { '999' => { '994' => [] } };
$VAR1 = { '999' => { '993' => [] } };
$VAR1 = { '999' => { '997' => [ '986', '987', '990', '984', '989', '988' ] } };
$VAR1 = { '995' => { '101' => [] } };
$VAR1 = { '995' => { '102' => [] } };
$VAR1 = { '995' => { '103' => [] } };
$VAR1 = { '995' => { '104' => [] } };
$VAR1 = { '995' => { '105' => [] } };
$VAR1 = { '995' => { '106' => [] } };
$VAR1 = { '995' => { '107' => [] } };
$VAR1 = { '994' => { '910' => [] } };
$VAR1 = { '993' => { '909' => [] } };
$VAR1 = { '993' => { '904' => [] } };
$VAR1 = { '994' => { '985' => [] } };
$VAR1 = { '994' => { '983' => [] } };
$VAR1 = { '993' => { '902' => [] } };
$VAR1 = { '999' => { '992' => [ '905' ] } };
A sample of the output is below:
'994' => {
'910' => [],
'985' => [],
'983' => []
},
'999' => {
'993' => [],
'992' => [
'905'
],
'997' => [
'984',
'986',
'987',
'988',
'989',
'990'
],
wow. thanks so much everyone (especially Axeman)! sorry for the lack of code or clarification, I was trying to generate a tree, and did try Hash::Merge, but could not for the life of me resolve the coined-995 problem of replacing the empty 995 with the non-empty 995; Axeman's solution works beautifully and I really appreciate the help/collaboration! (also tried the others and it either did the same thing as Hash::Merge, or it actually got rid of some branches).
some background on the input: had a set of hashes, each had keys (all same level) and two of which defined a) a parent to another, and b) itself (the rest were children), and so with a tree, i figured a hash was perfect, came up with a set of new hashes {a}->{b}->[c], and here we are...
again, thanks everyone and Axeman!