I'm trying to parse a JSON string which has been flattened from a relational dataset from the looks of it... Into a CSV in order to import into an Oracle database. I have name/value pairs similar to this (simplified) example
Device.1.Service.1.Channel.1.someProperty : 1,
Device.1.Service.1.Channel.1.someOtherProperty : "billy",
Device.1.Service.1.Channel.2.someProperty : 8,
Device.1.Service.1.Channel.2.someOtherProperty : "frank",
Device.1.Service.1.Channel.3.someProperty : 12,
Device.1.Service.1.Channel.3.someOtherProperty : "sam",
Device.1.Service.2.Channel.1.someProperty : 3,
Device.1.Service.2.Channel.1.someOtherProperty : "john",
EDIT: A .json file with a similar structure is produced per day per device. So when consolidated (Get-Content .\*.json -Raw) I see the same property names occur many times in console.
As part of the conversion, I'd like parts of the property name to become fields in the database. This will enable us to visualise the data better using dynamic slider filters etc. later.
| Device | Service | Channel | someOtherProperty |
| 1 | 1 | 1 | billy |
| 1 | 1 | 2 | frank |
| 1 | 1 | 3 | sam |
Right now, I'm using the cmdlet ConvertFrom-Json. I'm then selecting the fields (out of nearly 2000 possible fields) using wildcards. The number of channels for example is dynamic.
Get-Content .\*.json -Raw | ConvertFrom-Json |
Select Device.1.Service.1.Channel.?.someOtherProperty
Which returns a PSCustomObject. I'd like to figure out the channel number and use that as a derived field. Pseudo example:
Select #{n="Channel";e={$_.getCharAtWildcard()}},
$_.theValueofTheCurrentObject()
If I pipe the data selected (using wildcard) to Get-Member, the PSCostomObject contains method names as well as my field names (but not the values).
I'm confused about how I access the field name (since 'Name' gives blank rows), then extract the channel number (the character at wildcard position), and then the value to structure my output correctly.
Any pointers? Googled and have a lynda.com sub, but can't seem to find a solution to this specific problem - probably because I'm not using the correct terms?
## ANSGAR's SOLUTION - WORKS FOR SINGLE FILE ##
$dataDir = "C:\ps_json2csv\dummydata"
CD $dataDir
$dict = Get-Content .\*.json -Raw | ConvertFrom-Json | Select -Expand data
$p = 'DvbId'
$re = "frontend\.(\d+)\.logicalchannel\.(\d+)\.service\.(\d+)\..*?\.$p"
## modified regex to match my data, example string: FrontEnd.2.LogicalChannel.3.Service.1.stat.DvbId
$fullset = $dict.PSObject.Properties | Where-Object {
$_.Name -match $re
} | ForEach-Object {
$prop = [ordered]#{
FrontEnd = $matches[1]
LogicalChannel = $matches[2]
Service = $matches[3]
$p = $_.Value
}
New-Object -Type PSObject -Property $prop
}
## inspect $dict - its populated
## inspect $fullset - its empty! :(
Data of which is 2 files contained in C:\ps_json2csv\dummydata :
File1.json
{
"data": {
"Device.1.Service.1.ChannelInfo.Channel.1.Stats.someProperty" : "1",
"Device.1.Service.1.ChannelInfo.Channel.2.Stats.someProperty" : "8",
"Device.1.Service.1.ChannelInfo.Channel.3.Stats.someProperty" : "12",
"FrontEnd.2.LogicalChannel.3.Service.1.stat.DvbId" : "john",
"FrontEnd.2.LogicalChannel.3.Service.2.stat.DvbId" : "billy",
"FrontEnd.2.LogicalChannel.3.Service.3.stat.DvbId" : "frank",
"FrontEnd.2.LogicalChannel.4.Service.1.stat.DvbId" : "sam",
"Device.1.Service.2.ChannelInfo.Channel.1.Stats.someProperty" : "3",
"Some.value.im.not.intersted.in.just.yet": "Sat Jan 1 00:00:00 GMT 0001",
"foo.bar" : "0",
"random.stuff" : "hi there"
}
}
File2.json
{
"data": {
"Device.1.Service.1.ChannelInfo.Channel.1.Stats.someProperty" : "0",
"Device.1.Service.1.ChannelInfo.Channel.2.Stats.someProperty" : "7",
"Device.1.Service.1.ChannelInfo.Channel.3.Stats.someProperty" : "6",
"FrontEnd.2.LogicalChannel.3.Service.1.stat.DvbId" : "john",
"FrontEnd.2.LogicalChannel.3.Service.2.stat.DvbId" : "billy",
"FrontEnd.2.LogicalChannel.3.Service.3.stat.DvbId" : "frank",
"FrontEnd.2.LogicalChannel.4.Service.1.stat.DvbId" : "sam",
"Device.1.Service.2.ChannelInfo.Channel.1.Stats.someProperty" : "4",
"Some.value.im.not.intersted.in.just.yet": "Sun Jan 2 00:00:00 GMT 0001",
"foo.bar" : "0",
"random.stuff" : "hi there"
}
}
I would probably use a regular expression match to filter and extract your data:
$p = 'someOtherProperty'
$re = "device\.(\d+)\.service\.(\d+)\..*?\.channel\.(\d+)\..*?\.$p"
$fullset = $dict.PSObject.Properties | Where-Object {
$_.Name -match $re
} | ForEach-Object {
$prop = [ordered]#{
Device = $matches[1]
Service = $matches[2]
Channel = $matches[3]
$p = $_.Value
}
New-Object -Type PSObject -Property $prop
}
I'd rather convert a string of this syntax into a PSObject with the entire set of your properties. Like this:
$dict=Get-Content .\*.json -Raw | ConvertFrom-Json
$fullset=$dict.psobject.properties | % {
$parse=$_.name.split('.')
$parse+=,$_.value # since JSON values might be non-plain, we need to add the value as single object
$obj=new-object psobject
for ($i=0;$i -lt $parse.length;$i+=2) { # name-value
$v1=$parse[$i]
$v2=$parse[1+$i]
$obj | add-member -type noteproperty -name $v1 -value $v2
}
$obj
}
Then you parse $fullset like you would with a normal list with where {$_.device -eq '1' -and $_.someOtherProperty -ne $null} etc.
Related
I am trying to get the data into a csv from an InvokeWeb request. Currently there are only 2 different values with the individual values in it, but later also times more. It makes the headers of the table, and the correct number of columns, but throws the 2 different values into a cell in both rows. So how can I prevent it from looking like this? I have already read about AddMember or +=, but did not find the right way.
$valstring = iwr -Proxy http://my-proxy -ProxyUseDefaultCredentials -Method GET -Uri https://my-URL -Headers #{'ContentTyp' = 'application/json';"X-Api-Key" = "my-API-Key"} -UseBasicParsing | Select-Object Content | foreach {$_.Content}
$pathToOutputFile = "H:\Wichtiges\json2csvexport.csv"
$deckung = $valstring | ConvertFrom-Json | ForEach-Object {
foreach ($uuid in $_ ) {
[PsCustomObject] #{
name = $_.name | Out-String
version = $_.version | Out-String
active = $_.active | Out-String
}
}
}
$deckung | Export-Csv $pathToOutputFile -NoTypeInformation -UseCulture
The Json-Data looks like that
[
{
"name": "App1",
"version": "1.1.1",
"uuid": "a74a1969-f57d-437b-943f-d4c3dd3bc4bb",
"active": true,
"metrics": {
"high": 617,
"medium": 1250,
"low": 103,
}
},
{
"name": "App2",
"version": "1.3.1",
"uuid": "59eda14a-56a6-4dc6-8238-dd168bd0df3f",
"active": true,
"metrics": {
"high": 6,
"medium": 13,
"low": 0,
}
}
]
But it always comes out like that (second and third together and fourth and fifth row together):
|name | version | active |
|------|-----------|---------|
|App1 | 1.1.1 | true |
|App2 | 1.3.1 | true |
|App1 | 1.1.1 | true |
|App2 | 1.3.1 | true |
-----------------------------
How can I change it to get one per row that it looks like:
|name | version | active |
|------|-----------|---------|
|App1 | 1.1.1 | true |
|App2 | 1.3.1 | true |
-----------------------------
Thx
Kind regards
Edit (Solution):
That solution works for me, because I can put things from mectrics into the csv and give the Heading an other name:
Select-Object #{Name='Name';Expression={$_.name}},
#{Name='Version';Expression={$_.version}},
#{Name='Active';Expression={$_.Active}},
#{Name='Risk-High';Expression={$_.metrics.high}}
Edit (Another question):
How can I prevent numbers in the csv from being converted to dates?
Thx
Try the following:
$pathToOutputFile = "H:\Wichtiges\json2csvexport.csv"
# Note:
# * the use of Invoke-*RestMethod*, which has ConvertFrom-Json *built in*.
# * the (...) around it, to ensure that the array is *enumerated*.
(Invoke-RestMehod -Proxy http://my-proxy -ProxyUseDefaultCredentials -Method GET -Uri https://my-URL -Headers #{'ContentType' = 'application/json';"X-Api-Key" = "my-API-Key"}) |
Select-Object name, version, active |
Export-Csv $pathToOutputFile -NoTypeInformation -UseCulture
As for what you tried:
Your foreach ($uuid in $_ ) loop mistakenly used $_ - the array to loop over - instead of $uuid - the iteration variable containing the element at hand.
The simpler alternative, to explicit enumeration is to wrap a call to ConvertFrom-Json / Invoke-RestMethod in (...) in order to automatically force enumeration of an array being returned.
Note that this is no longer necessary in PowerShell (Core) 7+, where arrays are enumerated by default (and you need -NoEnumerate in order to send an array as a whole through the pipeline, which is the invariable default behavior in Windows PowerShell).
For simple stringification of a value, Out-String is the wrong tool, primarily because it - unexpectedly - adds a trailing newline to the output - see GitHub issue #14444.
Use a [string] cast instead; however, given that Export-Csv implicitly performs such a stringification for you, there's no need to do it explicitly, allowing you to use a simple Select-Object to pick the properties of interest, as shown above.
Guys this is my JSON file and I want to create a PowerShell script which will give me result like
I have used method like Get-Content and other but there are some issues with the JSON parsing. Please find what is my requirement I have explained in details below.
MyLocalMachineHome
LocalMachine = Sahil_LocalMachine
Second_MyLocalMachine = Sahil_MylocalMachine
Second_MyLocalMachine = ""
Staging
Second_Staging = Sahil;_Secconf
Staging = Sahil_Staging
third_staging = stsajiii
There is also one functionality which I would like to have if I want to get only variables of "staging".
I was using this function Get-Content -Raw -Path E:\shell\Powershell\1ReleasePipelines.json | ConvertFrom-Json | select -ExpandProperty variables on my original JSON file but somehow there is some kind of limit in storing string which I was getting from this method.
{
"environments": [
{
"id": 3,
"name": "MyLocalMachineHome",
"variableGroups": [],
"variables": {
"LocalMachine": {
"value": "Sahil_LocalMachine"
},
"Second_MyLocalMachine": {
"value": "Sahil_MylocalMachine"
},
"thirf_mylocal": {
"value": ""
}
}
},
{
"id": 7,
"name": "Staging",
"variableGroups": [],
"variables": {
"Second_Staging": {
"value": "Sahil;_Secconf"
},
"Staging": {
"value": "Sahil_Staging"
},
"third_staging": {
"value": "stsajiii"
}
}
}
]
}
If we assume that $json contains your JSON content, you can do the following ugly code:
$environment = 'staging'
$j = $json | ConvertFrom-Json
($j.environments | where name -eq $environment).variables | Foreach-Object {
$CurrentObject = $_
$CurrentObject | Get-Member -MemberType NoteProperty |
Select-Object -Expand Name | Foreach-Object {
$CurrentObject.$_.value
}
}
It appears your issue is that you don't know what variables are going to be contained within your JSON. So you can't easily use Select-Object variable or $object.variable. You need a dynamic approach.
If you know your variables ahead of time, things become simpler. You can store your variable names in an array and loop over them.
$variables = 'Second_Staging','Staging','third_staging'
$environment = 'staging'
$j = $json | ConvertFrom-Json
$jsonVars = ($j.environments | where name -eq $environment).variables
$variables | Foreach-Object {
$jsonVars.$_.value
}
View all the sub-properties of variables with format-list instead of format-table. Since the properties vary, format-table won't show all of them. There's a lot of sloppy object construction in json.
$a = get-content file.json
$a.environments.variables | format-table
LocalMachine Second_MyLocalMachine thirf_mylocal
------------ --------------------- -------------
#{value=Sahil_LocalMachine} #{value=Sahil_MylocalMachine} #{value=}
$a.environments.variables | format-list
LocalMachine : #{value=Sahil_LocalMachine}
Second_MyLocalMachine : #{value=Sahil_MylocalMachine}
thirf_mylocal : #{value=}
Second_Staging : #{value=Sahil;_Secconf}
Staging : #{value=Sahil_Staging}
third_staging : #{value=stsajiii}
Get the staging variables?
$a.environments | where name -eq staging | foreach variables
Second_Staging Staging third_staging
-------------- ------- -------------
#{value=Sahil;_Secconf} #{value=Sahil_Staging} #{value=stsajiii}
cls
start-transcript -path 'C:\E\Devops\PowerShell_Chapters\ABC.txt'
write-output "**********Variables of Release************"
get-content -raw -path 'C:\E\Devops\PowerShell_Chapters\Release.json'| Convertfrom-Json | Select -ExpandProperty variables
$json = get-content -raw -path 'C:\E\Devops\PowerShell_Chapters\Release.json'| Convertfrom-Json | Select -ExpandProperty environments
$EnvirnomentsVariables = get-content -raw -path 'C:\E\Devops\PowerShell_Chapters\Release.json'| Convertfrom-Json | Select -ExpandProperty environments |Select -ExpandProperty name
$ReleaseVariable = get-content -raw -path 'C:\E\Devops\PowerShell_Chapters\Release.json'| Convertfrom-Json | Select -ExpandProperty environments |Select -ExpandProperty variables
$i = 0
foreach($a in $EnvirnomentsVariables)
{
$ABC_Staging = $EnvirnomentsVariables[$i]
#write-output $ABC_Staging
if( $ABC_Staging -match "ABC Staging")
{
write-output "****************Variables of " $EnvirnomentsVariables[$i]*************"
#add-content 'C:\E\Devops\PowerShell_Chapters\ABC.txt' $EnvirnomentsVariables[$i]
# Set-content -path 'C:\E\Devops\PowerShell_Chapters\Sahil.json'| ConvertTo-Json | select $EnvirnomentsVariables[$i]
write-output $ReleaseVariable[$i]
# add-content 'C:\E\Devops\PowerShell_Chapters\ABC.txt' $ReleaseVariable[$i]
# Set-content -path 'C:\E\Devops\PowerShell_Chapters\Sahil.json'| ConvertTo-Json | select $ReleaseVariable[$i]
}
$i = $i + 1
}
stop-transcript
I am writing a PowerShell Script, which will read a json file having different sections, like job1, job2 and so on.. Now my objective is to read each section separately and to loop through it as a key value pair. I also need to maintain the order of the input file, because the jobs are scheduled in sequence. and these jobs run taking the values from the json file as input.
I tried using Powershell version 5.1, in which I created PSCustomObject but the order is getting sorted alphabetically, which I DON'T want.
Json File :
{ "Job1": [
{
"Ram" : "India",
"Anthony" : "London",
"Elena" : "Zurich"
}],
"Job2": [
{
"Build" : "fail",
"Anthony" : "right",
"Sam" : "left"
}]}
$json = Get-Content -Path C:\PowershellScripts\config_File.json |
ConvertFrom-Json
$obj = $json.Job1
$json.Job1 | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
$values = [PSCustomObject][ordered]#{Key = $key; Value = $obj."$key"}
$values
}
I am expecting to loop through each section separately and in the same order that's provided in the json file. For example looping through Job1 section and to fetch only the Values in the same order that's in the json file.
I will guarantee that this is not the best way to do this, but it works.
$json = Get-Content -Path C:\PowershellScripts\config_File.json |
ConvertFrom-Json
$out = ($json.Job1 | Format-List | Out-String).Trim() -replace "\s+(?=:)|(?<=:)\s+"
$out -split "\r?\n" | ForEach-Object {
[PSCustomObject]#{Key = $_.Split(":")[0]; Value = $_.Split(":")[1]}
}
Explanation:
The JSON object is first output using Format-List to produce the Property : Value format, which is piped to Out-String to make that output a single string. Trim() is used to remove surrounding white space.
The -replace removes all white space before and after : characters.
The -split \r?\n splits the single string into an array of lines. Each of those lines is then split by the : character (.Split(":")). The [0] index selects the string on the left side of the :. The [1] selects the string on the right side of the :.
Can you change the json schema?
I would probably make changes to the json schema before i tried to parse this (if possible of course).
Like this (changed only Job1):
$json = #"
{ "Job1": [
{
"Name": "Ram",
"Location" : "India"
},
{
"Name": "Anthony",
"Location": "London"
},
{
"Name": "Elena" ,
"Location": "Zurich"
}
],
"Job2": [
{
"Build" : "fail",
"Anthony" : "right",
"Sam" : "left"
}]}
"# | convertfrom-json
foreach ($obj in $json.Job1) {
$key = $obj.Name
$values = [PSCustomObject][ordered]#{Key = $key; Value = $obj."$key" }
$values
}
I'm trying to produce a JSON dictionary from a powershell object with formatting that the ConvertTo-Json cmdlet doesn't provide by default.
For example the ConvertTo-Json will take the output of
(get-counter '\Process(*)\% Processor Time').CounterSamples | select InstanceName, CookedValue
which looks like:
InstanceName CookedValue
------------ -----------
idle 92.02923730929
process1 3
process2 1
process3 0
process4 2
process5 0
process6 2
process7 0
.... ....
and produce a JSON object that looks like this:
[
{
"InstanceName": "idle",
"CookedValue": 92.02923730929
},
{
"InstanceName": "process1",
"CookedValue": 3
},
{
"InstanceName": "process2",
"CookedValue": 1
},
...
]
Would it be possible to instead format the JSON object like this?:
{
idle: 92.02923730929,
process1: 3,
process2: 1,
process3: 0,
process4: 2,
process5: 0,
}
Thanks
Create a new object with properties made up of the sample property values:
$properties = #{}
(Get-Counter '\Process(*)\% Processor Time').CounterSamples |ForEach-Object {
$properties[$_.InstanceName] = $_.CookedValue
}
New-Object psobject -Properties $properties |ConvertTo-Json
Since you could have multiple instances with the same process name, you might want to extract the process name and instance number from the counter Path instead:
$properties = #{}
(Get-Counter '\Process(*)\% Processor Time').CounterSamples |ForEach-Object {
$InstanceName = if($_.Path -match 'process\((.*#\d+)\)'){
$Matches[1]
}
else {
$_.InstanceName
}
$properties[$InstanceName] = $_.CookedValue
}
New-Object psobject -Property $properties |ConvertTo-Json
just pipe it to ConvertTo-Json
(get-counter '\Process(*)\% Processor Time').CounterSamples | select InstanceName, CookedValue | ConvertTo-Json
I cannot answer your second question of reformatting the JSON output
I'm trying to automate some data pipelines with Powershell, but I'm kinda stuck with converting a JSON list to a single cell per row in a CSV file. Hope some of you can help me out.
The JSON I get looks like the following:
{"result": [
{
"uid": "1",
"EducationHistory": []
},
{
"uid": "2",
"EducationHistory": []
},
{
"uid": "3",
"EducationHistory": []
},
{
"uid": "4",
"EducationHistory": {
"10466632": {
"euid": 10466632,
"degree": "Highschool",
"educationLevel": null
},
"10466634": {
"euid": 10466634,
"degree": "Law",
"educationLevel": "batchelor"
},
"10466635": {
"euid": 10466635,
"degree": "Law",
"educationLevel": "master"
}
}
},
{
"uid": "5",
"EducationHistory": {
"10482462": {
"euid": 10482462,
"degree": "IT",
"educationLevel": "master"
}
}
}
]
}
What I want to do is collect the educationLevels per uid in one column. So something like this:
uid | educationLevel
----+------------------
1 |
2 |
3 |
4 | barchelor, master
5 | master
Normally I would like Expandproperty to get down to a lower level, but this doesn't work for this case because every EducationHistory entry is behind a euid for that specific entry. Expanding every single one of them like in the example below isn't workable because of the number of records.
So I think I need something of a loop, but I don't know how. Hope you can help me. First post here and a Powershell newbie, so I hope my question is clear. Please let me know if you need more info.
The code for one entry, as example:
$json = Get-content -raw -path C:\TEMP\File.json
(ConvertFrom-Json -InputObject $json).result |
Select-Object uid,
#Expand one of the entries:
#{Name = "Edu.Level";E={$_.EducationHistory | Select-Object -
expandproperty 10466632 |Select-Object -expandpropert degree }} |
Format-Table
$content = Get-Content .\test.json
$result = ($content | ConvertFrom-Json).result
$totalResult = #()
foreach($res in $result) {
$tempArray = #()
if($res.EducationHistory -ne $null) {
$properties = $res.EducationHistory | Get-Member -MemberType NoteProperty
foreach($property in $properties) {
$eduLevel = $res.EducationHistory.$($property.Name).educationLevel
if(![String]::IsNullOrEmpty($eduLevel)) {
$tempArray += $eduLevel
}
}
}
$totalResult += [PSCustomObject]#{
uid = $res.uid
educationLevel = $tempArray -join ", "
}
}
$totalResult
This will output desired result for the input you have provided.
The trickiest part is the value of EducationHistory property. You have to use Get-Member cmdlet (see Get-Help Get-Member) to get the properties of the current object in loop. Then using the name of the property to access the educationLevel.
Your first question, my first answer I believe :) Similar to the last answer. You need to jump through the hoop of finding the object names in EducationalHistory to reference them.
$json = (Get-content C:\TEMP\File.json | ConvertFrom-Json).result
$results = #()
foreach ( $u in $json)
{
foreach ( $h in $u.EducationHistory)
{
$results += $h.PSObject.properties.Name | ForEach-Object{new-object PSObject -property #{ uid=$u.uid; degree=$h.$_.degree}}
}
}
$results | ConvertTo-Csv | select -skip 1