Trouble adding to hash in perl - perl

I am reading a file replacing data and returning output to json. When I try to add a new item to the hash I get the following error. Not a HASH reference When I use ref() I am getting HASH as the type.
I have tried.
my $json_data = decode_json($template);
$json_data->{CommandCenters}{NewItem} = ["haha","moredata"];
Gives the not a hash reference error
The $json_data is below.
{
"Location":"Arkansas",
"CommandCenters": [
{
"secretary": "jill",
"janitor": "mike"
}
],
}
I am looking for the following output after I add the element.
{
"Location":"Arkansas",
"city": "little rock"
"CommandCenters": [
{
"secretary": "jill",
"janitor": "mike"
},
{
"NewItem":["whatever","more data"]
}
],
}
If I use $json_data->{CommandCenters}[0]{NewItem} = ['whatever','sure']; I do not get an error but I get unexpected results.
The data is added but in the incorrect slot.
"commandcenters":
[
"secretary":"jill",
"janitor": "mike",
"newitem":
[
"whatever","sure"
],
]

To add a new element to an array, use push. As we
're dealing with an array reference, we need to dereference it first.
push #{ $json_data->{CommandCenters} }, { NewItem => ["haha", "moredata"] };

When I try to add a new item to the hash I get the following error. Not a HASH reference When I use ref() I am getting HASH as the type.
Attention to detail is a vital skill for a successful programmer. And you're missing something subtle here.
When you use ref(), I assume you're passing it your $json_data variable. And that is, indeed, a hash reference. But the line that generates your Not a HASH reference is this line:
$json_data->{CommandCenters}{NewItem} = ["haha","moredata"];
And that's not just treating $json_data as a hash reference ($json_data->{...}) it's also treating $json_data->{CommandCenters} as a hash reference. And that's where your problem is. $json_data->{CommandCenters} is an array reference, not a hash reference. It's generated from the bit of your JSON that looks like this:
"CommandCenters": [
{
"secretary": "jill",
"janitor": "mike"
}
]
And those [ .. ] mark it as an array not a hash. You can't add a new key/value pair to an array; you need to push() new data to the end of the array. Something like:
push #{ $json_data->{CommandCenters} }, { NewItem => ["haha", "moredata"] };
That will leave you with this data structure:
$VAR1 = {
'CommandCenters' => [
{
'janitor' => 'mike',
'secretary' => 'jill'
},
{
'NewItem' => [
'haha',
'moredata'
]
}
],
'Location' => 'Arkansas'
};
And encode_json() will turn that into the JSON that you want.

Related

Only keep items that match string in array in Azure Data Factory

I have a very large array, similar to this one:
{
"name":"latest_test",
"value":[
{
"name":"2016-06-27-12Z",
"type":"Folder"
},
{
"name":"2016-06-28-00Z",
"type":"Folder"
},
{
"name":"2016-06-28-12Z",
"type":"Folder"
},
{
"name":"2016-06-29-00Z",
"type":"Folder"
},
{
"name":"2016-06-29-12Z",
"type":"Folder"
}
]
}
I only want to keep the items that have 2016-06-29 in their name. Such that I have a new array that only consists of 2016-06-29-00Z and 2016-06-29-12Z.
I tried to use a filter with #contains(item(), '2016-06-29') but this returns 0.
item() is the entire object for an element in the array. In order to filter on a property have to specify the property also.
Change the filter condition to
#contains(item().name, '2016-06-29')

Powershell Append PSCustomObject to nested PSCustomObject

My problem is that I have a remote API giving me a JSON with 10 levels deep nesting and I need to add another array/hashtable/PSCustomObject (whatever you want to call it) to one of them.
The JSON looks like this:
{
"result": [
"modules": [
"data": {
"segments": [
{
"routes": {
"static": [
{
"destination": "10.0.0.0",
"netmask": "255.0.0.0"
},
{
"destination": "172.16.0.0",
"netmask": "255.240.0.0"
}
]
}
}
]
}
]
]
}
The return object is a Hashtable, however when I try to access the values in dot notation, it turns into PSCustomObject:
$statics = $result.result.modules.data.segments.routes.static
It won't let me use the other notation:
$statics = $result['result']['modules']['data']['segments']['routes']['static']
Error: Cannot index into a null array
The real tricky part (for me) is to to append a new hastable to the "static" hashtable in such a way that the rest of the hashtable remains intact.
$newroute = #{
destination = "1.1.1.1."
netmask = "255.255.255.255"
}
I would have used PHP or Python but for this project I must use Powershell and I'm running into all sorts of things where PS behaves different from what I expect.
Any help is greatly appreciated!
static is an array, you can add new items to it:
$newStaticRoute = [pscustomobject]#{
destination = '192.168.0.0'
netmask = '255.255.0.0'
}
$result.result.modules.data.segments.routes.static += $newStaticRoute

How do I do a use a map function to create a custom hash from an array in Perl?

I have some JavaScript:
var $things = [
{
"id": "1",
"image": "one.png"
},
{
"id": "2",
"image": "two.png"
},
];
It gets converted to a Perl array ($thingsJSON is a string representation of the above):
my $coder = JSON::XS->new->utf8;
my $things = $coder->decode($thingsJSON);
A want a map function on $things that will return a collection ($args) that looks like this:
(
image => "one.png",
image => "two.png"
)
I want to be able to pass the above as $args to another function:
$Bar->find($args)
Assuming what you actually want is an array containing a list you can pass to that function, which expects key/value pairs, this would work:
#args = map { (image => $_->{image}) } #$things;
for clarity, this is the same as
#args = map { ('image', $_->{image}) } #$things;
that is, map is just returning a list where all of the even-numbered elements are the string "image" and all of the odd-numbered elements are the value of the image key of some element in #$things.

How do I work globally on a complex data structure using subroutines?

Specifically my data structure looks like this
{
"SomeGuy" => {
date_and_time => "11-04-2013",
Id => 7,
nr => 52,
picks => [
{ "This is an option" => "Option3" },
{ "This is another option" => "Option4" },
{ "This is another option" => "Option1" },
{ "And another one" => "Something" },
],
time_of => "06:11 AM",
total => 1,
},
"Another Guy" => { ... },
}
This is output via Data::Dump. The actual data structure contains a lot more records like "SomeGuy". The structure is identical for all of them.
I populate this data structure this way:
$guys->{$profile}{options}[$total++]{$var} = $var2;
$guys->{$profile}{Id} = $i;
$guys->{$profile}{date_and_time} = get_date($Time[0]);
$guys->{$profile}{time_of} = $Time[1];
$guys->{$profile}{total} = keys (% {$guys->{$profile}{options}[0]});
$guys->{$profile}{nr} = $pNr;
Having all this, what I want to do next is operate on this data structure. I repeat that there many many records in the data structure.
When I output the contents of it I get it in jumbled order, as to say, not in the order that it was populated. I've tried this with Data::Dumper, Data::Dump and iterating through the records myself by hand.
I know that the methods in the Data namespace are notorious for this, that's why Data::Dumper provides a way to sort through a subroutine and Data::Dump provides a default one.
So I have the data structure. It looks like I expect it to be, I have all the data as I knew it should look in it, valid. I want to sort the records according to their Id field. My thinking is that I have to use a subroutine and essentially pass a reference of the data structure to it and do the sorting there.
sub sortt {
my $dref = shift #_;
foreach my $name ( sort { $dref->{$a}{Id} <=> $dref->{$b}{Id} } keys %$dref ) {
print "$data->{$name}{Id}: $name \n";
}
}
This gets called with (in the same scope where the structure is populated, so no worries there):
sortt(\$guys);
The error is:
Not a HASH reference at perlprogram.pl line 452
So I go and use ref in the subroutine to make sure I'm passing an actual reference. And it says REF.
Next I go into desperate mode and try some stupid things like calling it with:
sortt(\%$guys)
But if I'm not mistaking this just sends a copy to the subroutine and just sorts that copy locally, so no use there.
It's no use if I make a copy and return it from the subroutine, I just want to pass a reference of my data structure and sort it and have it reflect those changes globally (or in the calling scope per se). How would I do this?
Leaving aside your syntax problem with "Not a ref", you are approaching the problem from the wrong end in the first place. I'll leave small syntactic details to others (see Ikegami's comment).
You can NOT sort them at all, because $guys is a hash, not an array. Hashes are NOT ever sorted in Perl. If you want to sort it, your have three solutions:
Store an ordered list of names as a separate array.
my #ordered_names = sort { $guys->{$a}{Id} <=> $guys->{$b}{Id} } keys %$guys
Then you use the array for ordering and go back to hashref for individual records.
Add the name to the individual guy's hash, instead of outer hash reference. $guys should be an array reference. The downside to this approach is that you can't look up a person's record by their name any more - if that functionality is needed, use #1.
#codnodder's answer shows how to do that, if you don't care about accessing records by name.
Use a Tie::* module. (Tie::IxHash, Tie::Hash::Sorted). NOT recommended since it's slower.
Perl hashes are inherently unordered. There is no way you can sort them, or reorder them at all. You have to write code to to access the elements in the order you want.
Your sortt subroutine does nothing but print the ID and the name of each hash element, sorted by the ID. Except that it doesn't, because you are trying to use the variable $data when you have actually set up $dref. That is likely to be the cause of your Not a HASH reference error, although unless you show your entire code, or at least indicate which is perlprogram.pl line 452, then we cannot help further.
The best way to do what you want is to create an array of hash keys, which you can sort in whatever order you want. Like this
my #names_by_id = sort { $guys->{$a}{Id} <=> $guys->{$b}{Id} } keys %$guys;
Then you can use this to access the hash in sorted order, like this, which prints the same output as your sortt is intended to, but formatted a little more nicely
for my $name (#names_by_id) {
printf "%4d: %s\n", $guys->{$name}{Id}, $name;
}
If you want to do anything else with the hash elements in sorted order then you have to use this technique.
$guys is already a hash ref, so you just need sortt($guys)
If you want a sorted data structure, you need something like this:
my #guys_sorted =
map { { $_ => $guys->{$_} } }
sort { $guys->{$a}{Id} <=> $guys->{$b}{Id} } keys %$guys;
print(Dumper(\#guys_sorted));
Or, in a sub:
sub sortt {
# returns a SORTED ARRAY of HASHREFS
my $ref = shift;
return map { { $_ => $ref->{$_} } }
sort { $ref->{$a}{Id} <=> $ref->{$b}{Id} } keys %$ref;
}
print(Dumper([sortt($guys)]));
That's a pretty complex data structure. If you commonly use structures like this in your program, I suggest you take your Perl programming skills up a notch, and look into learning a bit of Object Oriented Perl.
Object Oriented Perl is fairly straight forward: Your object is merely that data structure you've previously created. Methods are merely subroutines that work with that object. Most methods are getter/setter methods to set up your structure.
Initially, it's a bit more writing, but once you get the hang of it, the extra writing is easily compensated by the saving is debugging and maintaining your code.
Object Oriented Perl does two things: It first makes sure that your structure is correct. For example:
$some_guy->{Picks}->[2]->{"this is an option"} = "Foo!";
Whoops! That should have been {picks}. Imagine trying to find that error in your code.
In OO-Perl, if I mistyped a method's name, the program will immediately pick it up:
$some_guy->picks(
{
"This is an option" -> "Foo!",
"This is option 2" => "Bar!",
}
)
If I had $some_guy->Picks, I would have gotten a runtime error.
It also makes you think of your structure as an object. For example, what are you sorting on? You're sorting on your Guys' IDs, and the guys are stored in a hash called %guys.
# $a and $b are hash keys from `%guys`.
# $guys{$a} and $guys{$b} represent the guy objects.
# I can use the id method to get they guys' IDs
sort_guys_by_id {
guys{$a}->id cmp guys{$b}->id; #That was easy!
}
Take a look at the tutorial. You'll find yourself writing better programs with fewer errors.
With your heart set on a sorted data structure, I recommend the following. It is a simple array of hashes and, rather than using the name string as the key for a single-element hash, it adds a new name key to each guy's data.
I hope that, with the Data::Dump output, it is self-explanatory. It is sorted by the Id field as you requested, but it still has the disadvantage that a separate index array would allow any ordering at all without modifying or copying the original hash data.
use strict;
use warnings;
use Data::Dump;
my $guys = {
"SomeGuy " => {
date_and_time => "11-04-2013",
Id => 7,
nr => 52,
picks => [
{ "This is an option" => "Option3" },
{ "This is another option" => "Option4" },
{ "This is another option" => "Option1" },
{ "And another one" => "Something" },
],
time_of => "06:11 AM",
total => 1,
},
"Another Guy" => { Id => 9, nr => 99, total => 6 },
"My Guy" => { Id => 1, nr => 42, total => 3 },
};
my #guys_sorted = map {
my $data = $guys->{$_};
$data->{name} = $_;
$data;
}
sort {
$guys->{$a}{Id} <=> $guys->{$b}{Id}
} keys %$guys;
dd \#guys_sorted;
output
[
{ Id => 1, name => "My Guy", nr => 42, total => 3 },
{
date_and_time => "11-04-2013",
Id => 7,
name => "SomeGuy ",
nr => 52,
picks => [
{ "This is an option" => "Option3" },
{ "This is another option" => "Option4" },
{ "This is another option" => "Option1" },
{ "And another one" => "Something" },
],
time_of => "06:11 AM",
total => 1,
},
{ Id => 9, name => "Another Guy", nr => 99, total => 6 },
]

Defining an array of anonymous objects in CoffeeScript

How do I define an array of anonymous objects in CoffeeScript? Is this possible at all, using the YAML syntax?
I know that having an array of named objects is quite easy:
items:[
item1:
name1:value1
item2:
name:value2
]
However, it would be a bit trickier, if those two objects had no names
Simple -- place a comma by itself in a column lower than that in which you define your objects.
a = [
nameA1: valueA1
nameA2: valueA2
nameA3: valueA3
,
nameB1: valueB1
nameB2: valueB2
nameB3: valueB3
]
Will become:
var a;
a = [
{
nameA1: valueA1,
nameA2: valueA2,
nameA3: valueA3
}, {
nameB1: valueB1,
nameB2: valueB2,
nameB3: valueB3
}
];
You can also add a coma between each object: 
items:[
item1:
name1:value1
,
item2:
name:value2
]
you can't:
this is some tricks:
items:[
(name:"value1")
(name:"value2")
]
another
items:[
true && name:"value1"
true && name:"value2"
]
this is the best:
items:[
{name:"value1"}
{name:"value2"}
]
I think the comma solution is better, but I figured I'd add this for completeness:
a = [
{
nameA1: valueA1
nameA2: valueA2
nameA3: valueA3
}
{
nameB1: valueB1
nameB2: valueB2
nameB3: valueB3
}
]
You can define variable while defining array, so an ugly answer would be:
a =
items: [
item1 =
name: 'value1'
item2 =
name: 'value2'
]
It would work, but you may get warnings about "defined, but not used variables (item1, item2)". Better way would be to use underscore, variable used to omit not used variables:
a =
items: [
_ =
name: 'value1'
_ =
name: 'value2'
]
console.log JSON.stringify(a) will produce this:
{
"items":[
{
"name":"value1"
},{
"name":"value2"
}
]
}
I'm very happy to report after a bit of fiddling that I could get this to compile just right:
items: [
nameA: subA
nameB: subB
,
nameX: subX
nameY: subY
]
It results it just what you'd expect: a list of two anonymous objects.
I ran into a related problem and found this solution. If you want an array of many single k/v objects without braces, just indent some of them. Seems to do the trick.
data = [
"2013-09-25T16:46:52.636Z":3,
"2013-09-25T16:47:52.636Z":6,
"2013-09-25T16:48:52.636Z":2,
"2013-09-25T16:49:52.636Z":7,
"2013-09-25T16:50:52.636Z":5,
"2013-09-25T16:51:52.636Z":2,
"2013-09-25T16:52:52.636Z":1,
"2013-09-25T16:53:52.636Z":3,
"2013-09-25T16:54:52.636Z":8,
"2013-09-25T16:55:52.636Z":9,
"2013-09-25T16:56:52.636Z":2,
"2013-09-25T16:57:52.636Z":5,
"2013-09-25T16:58:52.636Z":7
]
Produces:
coffee> data
[ { '2013-09-25T16:46:52.636Z': 3 },
{ '2013-09-25T16:47:52.636Z': 6 },
{ '2013-09-25T16:48:52.636Z': 2 },
{ '2013-09-25T16:49:52.636Z': 7 },
{ '2013-09-25T16:50:52.636Z': 5 },
{ '2013-09-25T16:51:52.636Z': 2 },
{ '2013-09-25T16:52:52.636Z': 1 },
{ '2013-09-25T16:53:52.636Z': 3 },
{ '2013-09-25T16:54:52.636Z': 8 },
{ '2013-09-25T16:55:52.636Z': 9 },
{ '2013-09-25T16:56:52.636Z': 2 },
{ '2013-09-25T16:57:52.636Z': 5 },
{ '2013-09-25T16:58:52.636Z': 7 } ]
It's counter-intuitive to me; you'd think that this would make sub-objects but I think the comma at the end of the line tells it to stop making properties on that object.
Not an answer to the OP's question, but just in case you're here for the same reason I was... If you're low on Mountain Dew and use '=' instead of ':', then Coffeescript will turn your array of objects into a flat array without a compile error:
data = [
one='one'
two='two'
,
one='1'
two='2'
]
Produces
['one', 'two', '1', '2']
Insert more Mountain Dew and replace the '=' with ':'.
Why not:
list = []
list.push
prop1: val
prop2: val
list.push
prop1: val
prop2: val
It's still a huge improvement to me over js, very easy to read, minimal and pretty safe to write.