I want to create a text document from following JSON document:
[
{
"id": 12345,
"url": "https://www.w3schools.com",
"person": {
"firstname": "John",
"lastname": "Doe"
},
"department": "IT"
},
{
"id": 12346,
"url": "https://www.w3schools.com",
"person": {
"firstname": "Anna",
"lastname": "Jackson"
},
"department": "LOG"
}
]
My JSON schema looks like the following:
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"url": { "type": "string", "format": "uri" },
"person": {
"type": "object",
"properties": {
"firstname": { "type": "string" },
"lastname": { "type": "string" }
}
},
"department": { "enum": [ "IT", "LOG"] }
}
}
}
The text document should be structured as follows:
pid: 12345
dep-abb: IT
surname: Doe
name: John
pid: 12346
dep-abb: LOG
surname: Jackson
name: Anna
I'm a Perl and JSON newbie and was searching for a Perl lib that can handle this approach by extending the schema (e.g. by txt_seq_no and txt_label). The labels in the text file should be sorted by txt_seq_no ASC and renamed by txt_label. Is is possible to solve that issue that simple? Then the schema could look like:
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer", "txt_seq_no"=10, "txt_label"="pid" },
"url": { "type": "string", "format": "uri" },
"person": {
"type": "object",
"properties": {
"firstname": { "type": "string", "txt_seq_no"=40, "txt_label"="name" },
"lastname": { "type": "string", "txt_seq_no"=30, "txt_label"="surname" }
}
},
"department": { "enum": [ "IT", "LOG", "PROD" ], "txt_seq_no"=20, "txt_label"="dep-abb" }
}
}
}
The basic approach is to use a JSON module to decode the JSON into a Perl data structure and then walk that structure to construct the output that you want.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use JSON;
my $json = JSON->new;
my $json_str = '[
{
"id": 12345,
"url": "https://www.w3schools.com",
"person": {
"firstname": "John",
"lastname": "Doe"
},
"department": "IT"
},
{
"id": 12346,
"url": "https://www.w3schools.com",
"person": {
"firstname": "Anna",
"lastname": "Jackson"
},
"department": "LOG"
}
]';
my $data = $json->decode($json_str);
for (#$data) {
say "pid: $_->{id}";
say "dep-abb: $_->{department}";
say "surname: $_->{person}{lastname}";
say "name: $_->{person}{firstname}\n";
}
Thanks all for tips and support!
I'm a totally beginner in Perl, feedback is welcome!
(I've ommited function parameter check to save space)
used packages:
use Data::Traverse;
use Data::Path;
sub transform
# key identifier[0]: mark JSON key that write into text file
# key identifier[1]: optional in JSON schema: new label in text file
my #KEY_IDENTIFIER = ("txt_seq_no", "txt_label");
# keys in JSON SCHEMA, can not be found in JSON document
my #DELETE_FROM_PATH = ( "items", "properties" );
# transform a JSON document into text file
# use identifier in JSON schema to define keys to transform to text
sub transform {
my ( $doc, $schema, $key_identifier) = #_;
if( not defined $key_identifier ) { $key_identifier = \#KEY_IDENTIFIER; }
my $transformator = get_transformator4schema( $schema, $key_identifier, $log );
my $data = get_data4transformator( $doc, $transformator, $log );
print_text( $data, $transformator, $key_identifier, $log );
}
sub get_transformator4schema
sub get_transformator4schema {
my ( $schema, $key_identifier) = #_;
my $t= Data::Traverse->new( $schema);
my %transformator;
$t->traverse(
sub {
if( $t->item_key eq #{$key_identifier}[0] or $t->item_key eq #{$key_identifier}[1]) {
my $path = $t->path;
my $key;
# delete path that only exists in schema, not in doc
foreach my $del_path (#DELETE_FROM_PATH) {
$path =~ s/\/$del_path//g;
}
# delete last element from path
if( $t->item_key eq #{$key_identifier}[0]) {
$key = #{$key_identifier}[0];
$path =~ s/\/#{$key_identifier}[0]$//g;
}
if( $t->item_key eq #{$key_identifier}[1]) {
$key = #{$key_identifier}[1];
$path =~ s/\/#{$key_identifier}[1]$//g;
}
# add key => { key => value }
if( not exists $transformator{$path} ) {
my %key_val = ( $key => $_ );
$transformator{$path} = \%key_val;
} else {
my $nested_hash = $transformator{$path};
$nested_hash->{$key} = $_;
}
}
}
);
return \%transformator;
}
sub get_data4transformator
# fetch needed data from document
sub get_data4transformator {
my ( $document, $transformator, $log ) = #_;
my #template;
foreach my $doc (#{$document}) {
my $doc_path= Data::Path->new( $doc );
my %th = ();
# load values for key = path
foreach my $path (keys %{$transformator}) {
my $val = $doc_path->get($path);
# add value from doc to hash
$th{$path} = $val;
}
push #template, \%th;
}
return \#template;
}
sub print_text:
sub print_text {
my ( $data, $transformator, $key_ids, $log ) = #_;
# sort by 1st item of $key_ids
my $sort_by = #{$key_ids}[0];
my $opt_name = #{$key_ids}[1];
print "\nsort data by '$sort_by' ASC\n";
my #sorted_keys = sort {
$transformator->{$a}->{$sort_by}
<=>
$transformator->{$b}->{$sort_by}
} keys %{$transformator};
foreach my $tdoc ( #{$data} ) {
print "\nread document item:\n";
foreach my $key ( #sorted_keys ) {
my $name = $key;
$name =~ s/^.*\/(\w+)$/$1/g;
my $alias_name = "";
if( defined $transformator->{$key}->{$opt_name} ) {
$alias_name = $transformator->{$key}->{$opt_name};
}
print " $name => $tdoc->{$key}";
if( not $alias_name eq "" ) {
print " alias '$alias_name'\n";
} else {
print "\n";
}
}
}
}
This functionality allows to control which key goes into the text file and its position. With at least one parameter txt_seq_no. The second parameter txt_label is optional and stores the alternative label in the textfile.
output:
id => 12345 alias 'pid'
department => IT alias 'dep-abb'
lastname => Doe
firstname => John alias 'name'
id => 12346 alias 'pid'
department => LOG alias 'dep-abb'
lastname => Jackson
firstname => Anna alias 'name'
id => 12347 alias 'pid'
department => PROD alias 'dep-abb'
lastname => Smith
firstname => Peter alias 'name'
Related
I have the following situation below. I'd like to add a name to each item in the array.
First item needs to have a name instead of being an anonymous object. How do I do this?
Items need to have an name derived from the Technical Name, so 988 instead of N988AB1.
Structure of array is changed to an object in the result.
Current situation:
{
"Category": [{
"TechnicalName": "N988AB1",
"Name": "House"
},
{
"TechnicalName": "H181AG3",
"Name": "Apartment"
},
{
"TechnicalName": "X123XY5",
"Name": "Villa"
}
]
}
Desired situation:
{
"Data": {
"988": {
"TechnicalName": "N988AB1",
"Name": "House"
},
"181": {
"TechnicalName": "H181AG3",
"Name": "Apartment"
},
"0123": {
"TechnicalName": "X0123XY5",
"Name": "Villa"
}
}
}
One way of tackling this: (You can copy and paste the full contents of the code box to try it out. I've commented what various steps are doing)
$ExistingJson = #"
{
"Category": [{
"TechnicalName": "N988AB1",
"Name": "House"
},
{
"TechnicalName": "H181AG3",
"Name": "Apartment"
},
{
"TechnicalName": "X123XY5",
"Name": "Villa"
}
]
}
"#
# Convert the json string to an object we can manipulate.
$ExistingObj = $ExistingJson | ConvertFrom-Json
# Create an empty hashtable for the new items we are creating.
$HT = #{}
foreach ($Category in $ExistingObj.Category) {
# Match any digits, grouped as "name", after any character [A-Za-z] which are followed by [A-Za-z] - this is pretty basic and may need to be changed if your TechnicalName string changes, or you want different information from it.
[System.Void]($Category.TechnicalName -match "(?<=[A-Za-z])(?<name>\d+)(?=[A-Za-z])")
$NewItem = [PSCustomObject]#{
TechnicalName = $Category.TechnicalName
Name = $Category.Name
}
# Add a new entry to the hashtable with the discovered digits by it's group name "name" and the object created above with the existing information as it's value.
$HT.Add($Matches["name"], $NewItem)
}
# Create a new object with a Data property with the value as the hashtable.
$NewObj = [PSCustomObject]#{
Data = $HT
}
# Convert it back to Json
$NewObj | ConvertTo-Json
Results in:
{
"Data": {
"988": {
"TechnicalName": "N988AB1",
"Name": "House"
},
"181": {
"TechnicalName": "H181AG3",
"Name": "Apartment"
},
"123": {
"TechnicalName": "X123XY5",
"Name": "Villa"
}
}
}
I have to filter JSON by IsNew parameter (true or false)
Part of the JSON is below:
{
"data": [
{
"type": "users",
"attributes": {
"first-name": "student21",
"last-name": "student21",
"username": "student21",
"role": "student",
"IsNew": true
},
"relationships": {
"groups": {
"data": [
{
"type": "groups",
"id": "123f"
}
]
}
}
},
{
"type": "users",
"attributes": {
"first-name": "student23",
"last-name": "student23",
"email": "",
"avatar-url": null,
"username": "student23",
"role": "student",
"IsNew": false
},
"relationships": {
"groups": {
"data": [
{
"type": "groups",
"id": "456"
}
]
}
}
}
]
}
I've tried the following expressions:
$..data..[?(#.IsNew == true)].username,
$..data..[?(#.IsNew == 'true')].username,
$..data..[?(#.IsNew == "true")].username
All those expressions don't return any result.
I need to extract usernames for students with "IsNew" == true and "IsNew" == false separetely.
To extract usernames for students with "IsNew" == true, use a JSON Extractor with following settings:
JSON Path Expression :$.data..attributes[?(#.IsNew =~ /.*true/i)].username
Match No. -1 [to get all the matches for multiple students]
To extract usernames for students with "IsNew" == false, use a JSON Extractor with following settings:
JSON Path Expression :$.data..attributes[?(#.IsNew =~ /.*false/i)].username
Match No. -1 [to get all the matches for multiple students]
Use following variables to process further:
${FalseStudent_matchNr} or ${FalseStudent} if you have used Match No other than -1 in JSON Extractor
${TrueStudent_matchNr} or ${TrueStudent} if you have used Match No other than -1 in JSON Extractor
I have a string like this:
$string = "PackageName1,1,C:\Path
PackageName2,12,C:\Path2
PackageName3,3,C:\Path3"
(is a file with multilines, when I get the content I have the string above)
I want to convert this string to Json:
[
{
Pacakge: "PackageName1",
Branch: = "1",
LocalPath = "C:\Path"
}
{
Pacakge: "PackageName2",
Branch: = "2",
LocalPath = "C:\Path2"
}
]
I can get the values with this code:
$spiltted = $string.Split(' ')
ForEach ($s in $splitted)
{
$values = $s.Split(',')
}
How can I add the Json keys to each value and convert it to Json object?
Thank you.
As your String looks like a csv without headers:
$string | ConvertFrom-Csv -Header Package,Branch,LocalPath|ConvertTo-Json
Sample output
[
{
"Package": "PackageName1",
"Branch": "1",
"LocalPath": "C:\\Path"
},
{
"Package": "PackageName2",
"Branch": "12",
"LocalPath": "C:\\Path2"
},
{
"Package": "PackageName3",
"Branch": "3",
"LocalPath": "C:\\Path3"
}
]
I have below sample nested json response and I need to convert this response with specific values into CSV file. Below is the sample nested Json response:
{
"transaction": {
"id": "TestTransID",
"testCode": "NEW",
"TestStatus": "SUCCESS",
"client": {
"TestNumber": "112112111"
},
"subject": {
"individual": {
"additionalAttributes": {
"extraid": "787877878"
},
"addressList": [
{
"city": "New York",
"country": {
"name": "United States"
},
"postalCode": "123456789",
"stateOrProvince": {
"codeValue": "NY"
}
}
],
"gender": "F",
"identificationDocumentList": [
{
"number": "1214558520",
"type": "TestId"
}
],
"name": {
"firstName": "Qusay TestFull",
"lastName": "TestLast",
"middleName": "Middle 3"
}
}
},
"PROCESSConfiguration": {
"id": 1
},
"testProductList": [
{
"product": {
"id": 00,
"name": "Test PROCESS",
"productCode": "EFG",
"disclaimer": "TestDisclaimer"
},
"testSourceResponseList": [
{
"testSource": {
"id": 1,
"name": "TEST"
},
"testSourceRecordList": [
{
"type": "TestRecord",
"alertReasonCode": "TESTS",
"alertReasonDescription": "ACTION LIST HIT - TEST",
"testSource": "TEST",
"varListNameFull": "TEST FULL NAME",
"varListNameShort": "TEST SHORT",
"varProgList": [
"SHORT"
],
"varListId": "3421",
"subject": {
"individual": {
"TestScore": {
"TestScore": 100,
"triggeredRule": "TestRule"
},
"aNameList": [
{
"fullName": " TestNameA",
"lastName": "TestNameA"
},
{
"firstName": "TestFirst",
"fullName": "TestFirst HUSAYN",
"lastName": "TestLast"
},
{
"firstName": "TestFirst",
"fullName": "TestFull",
"lastName": "TestLast"
},
{
"firstName": "TestFirst",
"fullName": "TestFull",
"lastName": "TestLast"
}
],
"birthList": [
{
"dateOfBirth": "12 Apr 1910",
"dateOfBirthVerified": "true"
}
],
"name": {
"firstName": "TestFirst",
"fullName": "TestFull",
"lastName": "TestLast"
},
"varNationality": [
{
"verified": "true"
}
],
"remarks": "remark1"
}
}
},
{
"testSource": "TEST",
"varListNameFull": "TEST FULL",
"varListNameShort": "TEST SHORT",
"varProgList": [
"XYZ"
],
"varListId": "1234",
"subject": {
"individual": {
"overallScore": {
"TestScore": 100,
"triggeredRule": "Testing"
},
"birthList": [
{
"dateOfBirth": "1965",
},
{
"dateOfBirth": "1966",
}
],
"name": {
"firstName": "TestFirst",
"fullName": "TestFull",
"lastName": "TestLast",
},
"varNationality": [
{
"verified": "true"
}
],
"remarks": "REMARK2"
}
}
}
]
}
]
}
],
}
}
I need to take response from ""PROCESSConfiguration": {
"id": 1"
from row # 40. If u'll take above code in notepad ++.
Also I need the response with respect to var value like first name, last name full name, DOB etc.
I am still unsure what is being asked for. Let me assume that you want a fully qualified path for each of the elements in the JSON file.
I started by making some small adjustments to the JSON based on http://jsonlint.com/.
Based on that, I did a proof of concept that works for ONE OBJECT like the JSON you posted. It works for THIS CASE. I wrapped the logic to deal with multiple objects making assumptions about when one object started and the next began. Also, logic would should be added to deal with nested series/array containing multiple properties (i.e. Threads in Get-Process) to handle a generic case. A single element with an array of values (like .transaction.varProgList in this case) is handled by putting a ‘;’ between them.
CSV files assume that all the objects are symmetric (have the same properties). There is no check to see that the properties for each object align with the properties of the other objects. Note the handling of nested series is related to this. You may see an example of this by uncommenting the [System.Collections.ICollection] section and trying something like (Get-Process r*) | Select-Object Name,Threads | Expand-NestedProperty | Out-File .\t.csv -Width 100000
The repro goes as follows where $a is the adjusted JSON content and the function is saved as Expand-NestedProperty.ps1.
# Load the function
. .\Expand-NestedProperty.ps1
# Create PowerShell object(s) based on the JSON
$b = $a | ConvertFrom-Json
# Create a file with the CSV contents.
$b | Expand-NestedProperty | Out-File -FilePath .\my.csv -Width 100000
Save this as Expand-NestedProperty.ps1
function Expand-NestedProperty {
[CmdletBinding()]
param (
[Parameter( Position=0,
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
ValueFromRemainingArguments=$false,
HelpMessage='Object required...' )]
$InputObject
)
begin {
function ExpandNestedProperty {
[CmdletBinding()]
param (
[Parameter( Position=0,
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
ValueFromRemainingArguments=$false,
HelpMessage='Object required...' )]
$InputObject,
[Parameter( Position=1,
Mandatory=$false,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$true,
ValueFromRemainingArguments=$false,
HelpMessage='String required...' )]
[string]
$FullyQualifiedName = ""
)
begin {
$localResults =#()
$FQN = $FullyQualifiedName
$nestedProperties = $null
}
process {
foreach ($obj in $InputObject.psobject.Properties) {
if ($(try {$obj.Value[0] -is [PSCustomObject]} catch {$false})) { # Catch 'Cannot index into a null array' for null values
# Nested properties
$FQN = "$($FullyQualifiedName).$($obj.Name)"
$nestedProperties = $obj.value | ExpandNestedProperty -FullyQualifiedName $FQN
}
elseif ($obj.Value -is [array]) {
# Array property
$FQN = "$($FullyQualifiedName).$($obj.Name)"
[psobject]$nestedProperties = #{
$FQN = ($obj.Value -join ';')
}
}
# Example of how to deal with generic case.
# This needed for the Get-Process values ([System.Collections.ReadOnlyCollectionBase] and [System.Diagnostics.FileVersionInfo]) that are not [array] collection type.
<#
elseif ($obj.Value -is [System.Collections.ICollection]) {
# Nested properties
$FQN = "$($FullyQualifiedName).$($obj.Name)"
$nestedProperties = $obj.value | ExpandNestedProperty -FullyQualifiedName $FQN
}
#>
else { # ($obj -is [PSNoteProperty]) for this case, but could be any type
$FQN = "$($FullyQualifiedName).$($obj.Name)"
[psobject]$nestedProperties = #{
$FQN = $obj.Value
}
}
$localResults += $nestedProperties
} #foreach $obj
}
end {
[pscustomobject]$localResults
}
} # function ExpandNestedProperty
$objectNumber = 0
$firstObject = #()
$otherObjects = #()
}
process {
if ($objectNumber -eq 0) {
$objectNumber++
$firstObject = $InputObject[0] | ExpandNestedProperty
}
else {
if ($InputObject -is [array]) {
foreach ($nextInputObject in $InputObject[1..-1]) {
$objectNumber++
$otherObjects += ,($nextInputObject | ExpandNestedProperty)
}
}
else {
$objectNumber++
$otherObjects += ,($InputObject | ExpandNestedProperty)
}
}
}
end {
# Output CSV header and a line for each object which was the specific requirement here.
# Could create an array of objects using $firstObject + $otherObjects that is then piped to Export-CSV if we want a generic case.
Write-Output "`"$($firstObject.Keys -join '","')`""
Write-Output "`"$($firstObject.Values -join '","')`""
foreach ($otherObject in $otherObjects) {
Write-Output "`"$($otherObject.Values -join '","')`""
}
}
} # function Expand-NestedProperty
I have a JSON Array in below format which I want to convert to JSON Object in (key,value) pair.
Since I am new to JSON I don't know how to achieve this.
{
"id": [
"100",
"101",
"102"
],
"Name": [
"ajit",
"amol",
"kiran"
],
"sex": [
"Male",
"Male",
"Male"
]
}
I want to convert above in the format like
[
{
"id": "100",
"Name": "ajit",
"Sex": "Male"
},
{
"id": "101",
"Name": "amol",
"Sex": "Male"
},
{
"id": "102",
"Name": "kiran",
"Sex": "Male"
}
]
Can you guys share your valuable thoughts on how to do this please?
Ajit
Assuming lots of things this works for the example,
var arrayOfObjects = [] ;
/**
* #params Object obj formated like the first one
*/
var i = 0,
arrayOfObjects = [] ;
for( prop in obj ) {
if ( prop instanceof Array ) {
for( i = 0 ; i < obj[prop].length ; ++i ) {
if ( typeof arrayOfObjects[i] !== undefined ) {
arrayOfObjects[i][prop] = obj[prop][i] ;
} else {
var newObj = {} ;
newObj[prop] = obj[prop][i] ;
arrayOfObjects.push(newObj) ;
}
}
}
}