ConvertTo-Json unboxing single item - json

I am working in PowerShell with a hashtable like so:
When I convert it to JSON notice that the "oranges" key does not contain brackets:
I have tried to accommodate for this when I create my hashtable by doing something like this:
foreach ($Group in ($input | Group fruit)) {
if ($Group.Count -eq 1) {
$hashtable[$Group.Name] = "{" + ($Group.Group | Select -Expand number) + "}"
} else {
$hashtable[$Group.Name] = ($Group.Group | Select -Expand number)
}
}
Which looks fine when I output it as a hashtable but then when I convert to JSON I get this:
I am trying to get that single item also surrounded in []. I found a few things here and one of them took me to this:
https://superuser.com/questions/414650/why-does-powershell-silently-convert-a-string-array-with-one-item-to-a-string
But I don't know how to target just that one key when it only contains a single item.

You want to ensure that all hashtable values are arrays (that is what the curly brackets in the hashtable output and the square brackets in the JSON mean).
Change this code:
if ($Group.Count -eq 1) {
$hashtable[$Group.Name] = "{" + ($Group.Group | Select -Expand number) + "}"
} else {
$hashtable[$Group.Name] = ($Group.Group | Select -Expand number)
}
into this:
$hashtable[$Group.Name] = #($Group.Group | Select -Expand number)
and the problem will disappear.

Related

how can I convert normal string to json object in Powershell

I have this table in normal string format ,
I want to convert this string to json object in PowerShell. ConvertTo-Json is not giving in correct format.
The answer depends somewhat on the true format of the table. If I assume this is tab delimited and that each column name doesn't have spaces I could pull it out something like:
$String =
#"
test test2 first others versions
------------------------------------------
Decimal 1 2 5 p
Decimal 1 3 8 p
Decimal 1 2 4 i
Decimal 2 2 6 p
Decimal 5 4 6 k
Decimal 2 5 2 p
"#
$String = $String -split "\r?\n"
$Headers = $String[0] -split "\s"
$Objects =
$String[2..($String.Count -1)] |
ConvertFrom-Csv -Header $Headers -Delimiter "`t" |
ConvertTo-Json
Above, -split the big string into lines, then look at the header line and -split it to get an array of column headers. Now skipping the first 2 elements in the $String array convert the remaining lines to objects using ConvertFrom-Csv and using the previously extracted $Headers array.
Note: This segment may also work and may be preferred for readability:
$Objects =
$String |
Select-Object -Skip 2 |
ConvertFrom-Csv -Header $Headers -Delimiter "`t" |
ConvertTo-Json
Note: Splitting on white space ( "\s" ) may cause issues if the field data may have whitespace itself.
However, given the ambiguity, a more certain approach might be more reliable, I would use the known start and end positions of the table's fields to do this.
Continuing with the above example string:
$String =
#"
test test2 first others versions
------------------------------------------
Decimal 1 2 5 p
Decimal 1 3 8 p
Decimal 1 2 4 i
Decimal 2 2 6 p
Decimal 5 4 6 k
Decimal 2 5 2 p
"#
$String = $String -Split "\r?\n"
$String |
Select-Object -Skip 2 |
ForEach-Object{
[PSCustomObject]#{
test = $_.SubString(0,7)
test2 = $_.SubString(8,1)
first = $_.SubString(14,1)
others = $_.SubString(20,1)
versions = $_.SubString(26,1)
}
}
Again, these positions may change depending if the columns are separated by spaces or tabs. My sample data may not be the same as yours and you may need to play with those positions. That said this is a very useful technique for deal with output from traditional console applications, very much worth knowing...
Note: Thanks Neko Nekoru; I added '?' to the RegEx to accommodate both Unix & Windows line ending styles.

Check for duplicate values for a specific JSON key

I have the following JSON records stored in a container
{"memberId":"123","city":"New York"}
{"memberId":"234","city":"Chicago"}
{"memberId":"345","city":"San Francisco"}
{"memberId":"123","city":"New York"}
{"memberId":"345","city":"San Francisco"}
I am looking to check if there is any duplication of the memberId - ideally return a true/false and then also return the duplicated values.
Desired Output:
true
123
345
Here's an efficient approach using inputs. It requires invoking jq with the -n command-line option. The idea is to create a dictionary that keeps count of each memberId string value.
The dictionary can be created as follows:
reduce (inputs|.memberId|tostring) as $id ({}; .[$id] += 1)
Thus, to produce a true/false indicator, followed by the duplicates if any, you could write:
reduce (inputs|.memberId|tostring) as $id ({}; .[$id] += 1)
| to_entries
| map(select(.value > 1))
| (length > 0), .[].key
(If all the .memberId values are known to be strings, then of course the call to tostring can be dropped. Conversely, if .memberId is both string and integer-valued, then the above program won't differentiate between occurrences of 1 and "1", for example.)
bow
The aforementioned dictionary is sometimes called a "bag of words" (https://en.wikipedia.org/wiki/Bag-of-words_model). This leads to the generic function:
def bow(stream):
reduce stream as $word ({}; .[($word|tostring)] += 1);
The solution can now be written more concisely:
bow(inputs.memberId)
| to_entries
| map(select(.value > 1))
| (length > 0), .[].key
For just the values which have duplicates, one could write the more efficient query:
bow(inputs.memberId)
| keys_unsorted[] as $k
| select(.[$k] > 1)
| $k

Powershell convertfrom-json separate two sets of values from JSON

There is a JSON URL which produces dynamic content, and there is one particular part of the JSON URL which I am trying to separate two values which have no title or name associated to them other than the parent title (accountUsage) and give them each a unique title, which I can then call upon in PowerShell.
Any ideas how to achieve this?
I need to convert this
accountUsage : #{10.10.2018=5; 09.10.2018=0; 08.10.2018=0; 07.10.2018=0; 06.10.2018=0; 05.10.2018=8; 04.10.2018=1; 03.10.2018=0;
02.10.2018=0; 01.10.2018=0}
Into this:
date
----
10.10.2018
value
----
5
date
----
09.10.2018
value
----
0
$json = '{"accountUsage":{"06.10.2018":0,"09.10.2018":0,"04.10.2018":1,"08.10.2018":0,"02.10.2018":0,"07.10.2018":0,"03.10.2018":0,"05.10.2018":8,"10.10.2018":5,"01.10.2018":0}}'
$data = $json | ConvertFrom-Json
$data.accountUsage | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]#{
date = $key
value = $data.accountUsage.$key
}
}
gives me a list of date/value pairs:
date value
---- -----
06.10.2018 0
09.10.2018 0
04.10.2018 1
08.10.2018 0
02.10.2018 0
07.10.2018 0
03.10.2018 0
05.10.2018 8
10.10.2018 5
01.10.2018 0
See this earlier answer of mine for some more insight into this.

Piping an empty object has a count of 1

I can't seem to get this function quite right. I want to pass it an object and if the object is empty, return 1, else count items in the object and increment by 1.
Assuming the following function "New-Test":
function New-Test
{
[cmdletbinding()]
Param
(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[object[]]$Object
#[object]$object
)
Begin
{
$oData=#()
}
Process
{
"Total objects: $($object.count)"
if($Object.count -gt 0)
{
$oData += [pscustomobject]#{
Name = $_.Name
Value = $_.Value
}
}
Else
{
Write-Verbose "No existing object to increment. Assuming first entry."
$oData = [pscustomobject]#{Value = 0}
}
}
End
{
$LatestName = ($oData | Sort-Object -Descending -Property Value | Select -First 1).value
[int]$intNum = [convert]::ToInt32($LatestName, 10)
$NextNumber = "{0:00}" -f ($intNum+1)
$NextNumber
}
}
And the following test hashtable:
#Create test hashtable:
$a = 00..08
$obj = #()
$a | foreach-object{
$obj +=[pscustomobject]#{
Name = "TestSting" + "{0:00}" -f $_
Value = "{0:00}" -f $_
}
}
As per the function above, if I pass it $Obj, I get:
$obj | New-Test -Verbose
Total objects: 1
Total objects: 1
Total objects: 1
Total objects: 1
Total objects: 1
Total objects: 1
Total objects: 1
Total objects: 1
Total objects: 1
09
Which is as expected. However, if I pass it $Obj2:
#Create empty hash
$obj2 = $null
$obj2 = #{}
$obj2 | New-Test -Verbose
I get:
Total objects: 1
Exception calling "ToInt32" with "2" argument(s): "Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: startIndex"
At line:33 char:9
+ [int]$intNum = [convert]::ToInt32($LatestName, 10)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentOutOfRangeException
01
I don't understand why $object.count is 1, when there's nothing in the hashtable.
If I change the parameter, $object's type from [object[]] to [object], the empty hashtable test results in:
$obj2 | New-Test -Verbose
Total objects: 0
VERBOSE: No existing object to increment. Assuming first entry.
01
Which is what I'd expect, however, if I run the first test, it results in:
$obj | New-Test -Verbose
Total objects:
VERBOSE: No existing object to increment. Assuming first entry.
Total objects:
VERBOSE: No existing object to increment. Assuming first entry.
This time $objects has nothing in it.
I'm sure it's simple, but I can't fathom this one out. Any help is appreciated.
P.S. PowerShell 5.1
$obj2 is a hashtable, not an array. Hashtables are not enumerated by default, so the hashtable itself is the one object. If you want to loop through an hashtable using the pipeline you need to use $obj2.GetEnumerator().
#{"hello"="world";"foo"="bar"} | Measure-Object | Select-Object Count
Count
-----
1
#{"hello"="world";"foo"="bar"}.GetEnumerator() | Measure-Object | Select-Object Count
Count
-----
2

Splitting one line of a Csv into multiple lines in PowerShell

I have a Csv which looks something like this:
No,BundleNo,Grossweight,Pieces,Tareweight,Netweight
1,Q4021317/09,193700,1614,646,193054
2,Q4021386/07,206400,1720,688,205712
What I first need to do is do some maths with the Netweight column to get two values, $x and $y.
Then I need to split each line in the Csv, into $x lines of data, where each line of data will look like "AL,$y"
In the below example, I get the values of $x and $y for each row successfully. The trouble comes when trying to then split each row into $x rows...:
$fileContent = Import-Csv $File
$x = ( $fileContent | ForEach-Object { ( [System.Math]::Round( $_.Netweight /25000 ) ) } )
$y = ( $fileContent | ForEach-Object { $_.Netweight / ( [System.Math]::Round( $_.Netweight /25000 ) ) } )
$NumRows = ( $fileContent | ForEach-Object { (1..$x) } )
$rows = ( $NumRows | ForEach-Object { "Al,$y" } )
This code works as I would hope when there is one row of data in the Csv. I.e. if $x = 8 and $y = 20, it would return 8 rows of data which look like "AL,20".
However, I get an error message when there is more than row in the Csv:
Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32".
I hope I've explained that OK and any help is appreciated.
Thanks,
John
Instead of using ForEach-Object over and over again, just iterate over the csv once and generate your $x and $y results one at a time:
$fileContent = #'
No,BundleNo,Grossweight,Pieces,Tareweight,Netweight
1,Q4021317/09,193700,1614,646,193054
2,Q4021386/07,206400,1720,688,205712
'# | ConvertFrom-Csv
foreach($line in $fileContent){
$x = [System.Math]::Round( $line.Netweight / 25000 )
if($x -ne 0){
$y = $line.Netweight / $x
1..$x |ForEach-Object {"AL,$y"}
}
}
Resulting in $x number of "AL,$y" strings per row:
AL,24131.75
AL,24131.75
AL,24131.75
AL,24131.75
AL,24131.75
AL,24131.75
AL,24131.75
AL,24131.75
AL,25714
AL,25714
AL,25714
AL,25714
AL,25714
AL,25714
AL,25714
AL,25714