3

I have searched looking for examples of converting complex json (embedded arrays] into csv file(s) using Powershell. The goal is to accept json data into a MSAccess database. MSAccess does not provide an intrinsic function to do this. I am new to Powershell and json, but I did discover the ConvertFrom-JSON cmdlet that got my interest. The best info I have found is the

Flatten-Object function by iRon

in response to this article

PowerShell convert nested JSON array into separate columns in CSV file

While this function works to create a single csv, I am interested in creating multiple csv files if there are embedded arrays in the json. The idea is to create a csv file for data at each level. Level 2 and lower will require a link field (id/name) to be used as a primary key in level1, and as foreign key in level2. A PK field at the level2, would be included as a foreign key at level3 and so on. Since Access can import csv data to a table, my feeling is that getting the data into "normalized" csv files would be a repeatable method to get json data into an MSAccess database.

So with respect to my objective and the Flatten-Object function, I am looking for advice/direction on the following:

  • Could the function be adjusted/used to identify
  • the levels in the json file,
  • to create a csv for each of those levels with a selectable PK field(s) to relate
  • the csv data files in a normalized manner for import to MSAccess??

I do realize that some human intervention will be required for each json file. So I'm looking for an approach that simplifies the effort and is repeatable.

I have created a simple script to take a simple json file (no embedded array) and convert it to CSV. I have used the Shell command in vba to execute the PS script.

    <#CarsBasic.ps1
.DESCRIPTION
This script takes the cars.json file and reads it into memory
Converts it from Json, then selects id,manufacturer,year from the result
and exports the data to C:\Programs\CarsJack.csv as a csv file with header
#>
 (Get-Content C:\Programs\MendipDataSystems\JSONParser\Files\Cars.json -Raw | 
 ConvertFrom-Json)  |Select id,manufacturer,year | 
  Export-CSV  c:\programs\CarsJack.csv -NoTypeInformation

Thanks in advance.

I have adjusted this post based on request/comment by iRon.

Sample json file that has Squad, SquadMember and SquadMemberPower levels. I would like to get a Squad.csv that has Squad info, and a SquadMember.csv that has the Squadname and each of the Member details, and a SquadmemberPower csv that has the SquadName and the Member Name identifying to whom that Power belongs. in effect, these 3 csv files would be loaded into MSAccess as 3 normalized tables. This is my test case, but I'd like a more general, reusable approach--if possible. Here is the MultiSquad.json

[{
    "squadName": "Super hero squad Alpha",
    "homeTown": "Metro City",
    "formed": 2016,
    "secretBase": "Large tent in the forest",
    "active": "True",
    "members": [{
        "name": "Molecule Man",
        "age": 29,
        "secretIdentity": "Dan Jukes",
        "powers": ["Radiation resistance",
        "Turning tiny",
        "Radiation blast"]
    },
    {
        "name": "Madame Uppercut",
        "age": 39,
        "secretIdentity": "Jane Wilson",
        "powers": ["Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"]
    },
    {
        "name": "Eternal Flame",
        "age": 1000000,
        "secretIdentity": "Unknown",
        "powers": ["Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"]
    }]
},
{
    "squadName": "Second squad Baker",
    "homeTown": "Metro Toronto",
    "formed": 2017,
    "secretBase": "CN tower",
    "active": "True",
    "members": [{
        "name": "Kathleen Wynne",
        "age": 49,
        "secretIdentity": "Cyan Arrah",
        "powers": ["XRay vision",
        "Invisibility",
        "Radiation blast"]
    },
    {
        "name": "Madame Butterfly",
        "age": 27,
        "secretIdentity": "Iman Angel",
        "powers": ["Magical hearing",
        "Fantastic ideas"]
    },
    {
        "name": "Gassy Misty Cloud",
        "age": 1000,
        "secretIdentity": "Puff of Smoke",
        "powers": ["Immortality",
        "Heat and Flame Immunity",
        "Impeccable hearing",
        "Xray Vision",
        "Able to jump tall buildings",
        "Teleportation",
        "Intergalactic travel"]
    }]
}]

Expected Output: 3 csv files

1) Squad.csv with fields "squadName","homeTown","formed","secretBase","active"

2) SquadMembers.csv with fields "squadName","name","age","secretIdentity"

3)SquadMemberPowers.csv with fields "Name","powers"

Orange
  • 73
  • 1
  • 1
  • 7
  • Hello Orange, can you give an example of an input file and expected output files? – iRon Oct 07 '17 at 08:33
  • Thanks for responding. The sample I have been working with as a test case is???? I tried to post a sample json, but it was too many characters. I'm not sure how to attach a file to a post here.. – Orange Oct 07 '17 at 11:28

3 Answers3

1

First get the json to an object:

$obj = Get-Content C:/input.json | ConvertFrom-Json

Then you have at least two ways how to select the items you want.

Simple selection:

$obj | select squadName, homeTown, formed, secretBase, active | Convertto-csv > c:\squads.csv

Complex selection:

$members = $obj | foreach {

    $squadName = $_.squadName

    $_.members | foreach {
        [pscustomobject]@{
            squadName = $squadName
            name = $_.name
            age = $_.age
            secretIdentity = $_.secretIdentity
        }
    }   
}
$members | ConvertTo-Csv > c:\members.csv

$powers = $obj.members | foreach {
    $memberName = $_.name
    $_.powers | foreach {
        [pscustomobject]@{
            name = $memberName
            power = $_
        }
    }
}
$powers | ConvertTo-Csv > c:\powers.csv
Mark Toman
  • 3,090
  • 2
  • 17
  • 18
  • Thanks mtman. I tried your code. It works, but I get powers as a comma separated list in the SquadMembers.csv. I really want a separate SquadMemberPowers.csv that shows membername, power as individual records. I've tried adjusting the script, but seem to be lost in the forEach loops . I get an empty csv (the header is correct) # extracting the power info goes here need powers for current member of current squad $powers = $obj | foreach { $Name = $_.name $_.powers | foreach { [pscustomobject]@{ name = $name power =$_.power } } } – Orange Oct 07 '17 at 14:49
  • For mtman and iRon (and others): Is it possible through use of properties etc to get similar result without having to use the explicit names in the script? That is, could I parse a json file and get the number of levels involved; the names of those levels and the field names and values. If this wasn't squads and members etc but Orders and details or any other--could this be set up without knowing the explicit names of the specific file. I'm trying to see if there is a general solution to parsing some json files. Thanks in advance. – Orange Oct 08 '17 at 18:31
  • @Orange You can iterate $obj recursively and determine using `.GetType()` if a current object is an array or PSCustomObject. In case it's an array, you iterate it and call the recursive function for each item. In case it's a PSCO, you get its members by `.psobject.properties`. – Mark Toman Oct 08 '17 at 20:53
  • Thanks mtman. As you can see I'm new to Powershell - still trying to understand commands and syntax. I'm trying to find/get/create a generic json parser (with some manual effort/components).My sample file had embedded arrays which was a hurdle. I'm trying to sort out how to look at the json and determine how many levels are involved and then see if I can get the fields and values at each level into separate csv without knowing/using the names used in the json. PS knows names and values, so there must be a way. I do appreciate your assistance, I wish I was more conversant in the terminology. – Orange Oct 08 '17 at 23:03
  • @Orange You can go through a json without knowing its property names as I hinted in my last comment, the harder part is how to determine which properties go to a separate file. In your case, you can say that any property of the parent that has "name" in its name will be added to each child. Such step makes it a non-generic converter, BTW. If the answer solves your original question, click "accept". If you have a new question, post it as a separate one. – Mark Toman Oct 09 '17 at 03:48
  • Thanks mtman. I agree, the user would have to know/determine which fields from which levels would make a normalized table and set the proper relationship. Perhaps general solution is not correct term. User would have to identify proper values for parameter(s), then invoke the code. I just found the check mark is "accept' and have done so. If you have tutorials or sites to help "newbie" to learn PS through example, I'd be very grateful. I am not a systems/network person, my background is database. I will review GetType() and properties. Can you identify a question as "related to another"? – Orange Oct 09 '17 at 12:40
1

Specific solution

Assuming that $JSON contains your JSON object:

$Squads = @(); $SquadMembers = @(); $SquadMemberPowers = @()

ForEach ($Squad In $JSON) {
    $Squads += New-Object PSObject ($Squad | Select squadName, homeTown, formed, secretBase, active)
    ForEach ($member In $Squad.members) {
        $SquadMembers += New-Object PSObject ($member | Select @{label = "squadName" ;expression = {$Squad.squadName}}, name, age, secretIdentity)
        ForEach ($power In $member.powers) {
            $SquadMemberPowers += New-Object PSObject ($member | Select @{label = "name" ;expression = {$member.name}}, @{label = "powers" ;expression = {$power}})
        }
    }
}

$Squads | Export-CSV ".\Squad.csv" -NoTypeInformation
$SquadMembers | Export-CSV ".\SquadMembers.csv" -NoTypeInformation
$SquadMemberPowers | Export-CSV ".\SquadMemberPowers.csv" -NoTypeInformation

General solution

With regards to a general (reusable) solution, I don't think that your request is general enough for that: at the members level you have an array with hashtables that you want to enumerate, at the powers level you like to transpose the array and than you want to pickup some properties from the parent that are not common (squadname vs name. You might consider here to refer to the first property but hashtables in PowerShell do not always stay in order, see: Powershell Hashtables Key Order).
In other words, for a general solution you will need to supply so many arguments that there will not much of an added value in comparison specific script as purposed above and changing it's adjusting it's functions and variables.

iRon
  • 20,463
  • 10
  • 53
  • 79
  • Thanks iRon. For the Powers csv, I am looking for the membername and power as individual records suited to import directly into a database table. – Orange Oct 08 '17 at 18:26
  • iRon this is what the records in Powers file should be like "name","power" "Molecule Man","Radiation resistance" "Molecule Man","Turning tiny" etc. one name and one power in each record. Sorry if I have been unclear. Thanks for your help. I am looking for a general solution as per previous comment, if you have some ideas. "Molecule Man","Radiation blast" – Orange Oct 08 '17 at 21:04
  • I think I am a little word blind, scripts are easier to read for me than full descriptions... Anyway, I have changed the the answer accordingly. – iRon Oct 09 '17 at 11:34
  • I wish I was conversant with PS scripts.Thank you for your answers-very helpful. I'm trying to learn the terminology by identifying what you have done in script with a plain English statement--in hopes of recognizing the approach (thought process) you take. I like the Flatten-Object and was hoping to get some sort of function/script that you could plug file specific values in to get the desired csv(s). A routine that it could be used in various situations. The user would have to know the values to supply to the parameters, but the routine could be reusable. Is it possible? – Orange Oct 09 '17 at 12:51
  • iRon --forgot to say your latest script produces the desired output for all 3 levels. If you know sites or articles to help a new PS user to get familiar with concepts, syntax through examples, they would be much appreciated. Thanks again. – Orange Oct 09 '17 at 13:00
  • I got most of my PowerShell knowledge from a book ["Proffesional Windows PowerShell"](http://www.wrox.com/WileyCDA/WroxTitle/Professional-Windows-PowerShell.productCd-0471946931.html) and just doing it: there are a lot of PowerShell snippets on the (StackOverflow) internet, instead of just copying them, try to understand them, rewrite them and add your own features... Btw. your up-vote will be appreciated, that are the [kudos](https://en.wikipedia.org/wiki/Kudos) were most of us are doing it for... – iRon Oct 09 '17 at 15:17
  • iRon I tried upping the vote but because I'm new (low reputation [3]) it appears my vote is hidden/not permanent?? There seems to be a lot of "details " surrounding stackoverflow that aren't obvious to new participants. – Orange Oct 09 '17 at 15:41
  • Thanks, I got an additional +15 points although the up-vote itself is indeed not visible. – iRon Oct 09 '17 at 15:51
0

The below command can be used for separating the csv data into columns with the delimiter ",".

For Example: Import-Csv "C:\Result.csv" -delimiter "," | Sort-Object _from -Unique | Export-csv "C:\FINAL_REPORT.csv"