I need to interact with an API that is expecting an array of objects, among other parameters. Example:
{
"fields": {
"somefield": "somevalue",
"someobject": {
"name": "foobar"
},
"versions": [
{
"name": "1.0"
}
]
}
}
With the help of this answer, I've tried two different ways of handling this. I've combined them into a single code example:
$versionName = New-Object -TypeName PSObject
$versionName | Add-Member -Name "name" -MemberType NoteProperty -Value "1.0"
$versionName2 = #{}
$versionName2.name = "1.0"
$postIssueBody = #{}
$postIssueBody.fields = #{}
$postIssueBody.fields.somefield = "somevalue"
$postIssueBody.fields.someobject = #{}
$postIssueBody.fields.someobject.name = "foobar"
$postIssueBody.fields.version = #($versionName)
$postIssueBody.fields.version2 = #()
$postIssueBody.fields.version2 += [pscustomobject]$versionName2
$postIssueRequestJson = $postIssueBody | ConvertTo-Json
$postIssueRequestJson
This results in the following output:
{
"fields": {
"somefield": "somevalue",
"someobject": {
"name": "foobar"
},
"version": [
"#{name=1.0}"
],
"version2": [
"#{name=1.0}"
]
}
}
As you can see, that's not going to fly as valid JSON. What is the best way to handle this assignment so that the version names are properly formed after going through ConvertTo-Json?
The ConvertTo-Json function has a switch called Depth. It informs the Convert function how deep it should go when converting data to the JSON format. By default, it is set at 2. Since the data that isn't being converted properly sits at a depth of 3, we simply need to set the depth to that, like so:
$postIssueRequestJson = $postIssueBody | ConvertTo-Json -Depth 3
And now we have well-formed JSON.
{
"fields": {
"somefields": "somevalue",
"someobject": {
"name": "foobar"
},
"versions": [
{
"name": "1.0"
}
]
}
}
Ok, I think I understand. So you need a string that starts with "versions": and is followed by an array of objects, yes? So, let's start with an empty array.
$Array = #()
Then we can create objects, and add those to the array:
$Array += [PSCustomObject]#{"Name1"="1.0.0"}
$Array += [PSCustomObject]#{"Name2"="3.10.0"}
Now we have a PowerShell array with PSCustomObjects in it. We can pipe that to ConvertTo-JSON and it will output:
[
{
"Name1": "1.0.0"
},
{
"Name2": "3.10.0"
}
]
Which is the array of objects you wanted. If you want an object to have that as it's value you could simply create another object to do that with:
$Versions = [PSCustomObject]#{'versions'=$Array}
Then you can convert that to JSON if you want and get:
{
"versions": [
{
"Name1": "1.0.0"
},
{
"Name2": "3.10.0"
}
]
}
That's what you were looking for, right? Or if you really want it on one line:
PS C:\> ($Versions|convertto-json).split() -join ""
{"versions":[{"Name1":"1.0.0"},{"Name2":"3.10.0"}]}
To be formatted exactly as your first example we would have to get rid of the { } surrounding that result I suppose, you can do that with Trim() as such:
PS C:\> ($Versions|convertto-json).trim("{}").split() -join ""
"versions":[{"Name1":"1.0.0"},{"Name2":"3.10.0"}]
Edit: Ok, so you just need to add objects as property values of other objects as needed, much like I did for setting the array as a value in the object in my example.
I think the easiest way to understand what needs to be done is to take your example (minus the last comma, since that makes it throw errors), and pipe it into ConvertFrom-JSON and assign it a variable. Then you can see how that is formed in Powersehll. Once I do that (I named my variable $JSON), I can see that $JSON has 1 NoteProperty of 'fields'. That NoteProperty has 3 NoteProperties of 'somefield', 'someobject', and 'versions'. When I do a $JSON.fields|Get-Member I find out more about those.
somefield is just a String. That will be easy enough to deal with.
someobject is a PSCustomObject, basically a HashTable where name=foobar.
versions just shows that it's a System.Object, so I'll do $JSON.fields.versions.GetType() and it shows that the basetpe is System.Array. After looking at versions it looks like an array with 1 object in it, and that object has one noteproperty that is a string (like the first object we had).
So, there's two ways to go about doing this. You can either try and create your objects and array in-line, or you can make them ahead of time, starting at the deepest nested layer, and work your way up. I'll be showing you the later.
$name = [PSCustomObject]#{'name'='1.0'}
$versions=#($name)
$Someobject = [PSCustomObject]#{'name'='foobar'}
$Fields = [PSCustomObject]#{
'somefields'='somevalue'
'someobject'=$someobject
'versions'=$versions}
$NewJSON = [PSCustomObject]#{'Fields'=$fields}
$NewJSON | ConvertTo-Json
Related
I am trying to loop around all of our subscriptions and get Policy Exemptions, but only get the ones that we have created. The loop appears fine, but the Match element appears to bring back some Exemptions that don't meet the -Match criteria.
$allSubscriptions = Get-AzSubscription
$baseFolder = "C:\source\PowerShell Exemptions Dump\"
# loop subscriptions
foreach($sub in $allSubscriptions){
$subName = $sub.Name
# Get Exemptions at Sub level
Set-AzContext -Subscription $subName
# Write to File
$exemptionsIn = Get-AzPolicyExemption|ConvertTo-Json
$fileName = $baseFolder + $subName + ".json"
$exemptionsOut = ''
foreach($ex in $exemptionsIn|ConvertFrom-Json){
if($ex.Properties.PolicyAssignmentId -Match "abc") {
$exemptionsOut += $ex|ConvertTo-Json
}
}
if ($exemptionsOut -ne '') {
$exemptionsOut | Out-File -filepath $fileName
$exemptionsOut = ''
}
}
It does work to a certain extent i.e. if a Subscription has a 0% match in everything it brings back, then it doesn't create a file. but it appears if it finds one match, then it saves Exemptions to the file that don't match.
Here is some example Json that was saved to one of the files:
[
{
"Properties": {
"PolicyAssignmentId": "/providers/Microsoft.Management/managementGroups/abc-mg/providers/Microsoft.Authorization/policyAssignments/abc-mg",
"PolicyDefinitionReferenceIds": "",
"ExemptionCategory": "Waiver",
"DisplayName": "abc - abc-mg Policy Assignment",
"Description": "AIB Testing",
"ExpiresOn": "\/Date(1662134400000)\/",
"Metadata": ""
},
"SystemData": null,
"Name": "456",
"ResourceId": "/subscriptions/123/providers/Microsoft.Authorization/policyExemptions/789",
"ResourceName": "456",
"ResourceGroupName": null,
"ResourceType": "Microsoft.Authorization/policyExemptions",
"SubscriptionId": "123"
},
{
"Properties": {
"PolicyAssignmentId": "/providers/Microsoft.Management/managementGroups/root-mg/providers/Microsoft.Authorization/policyAssignments/111",
"PolicyDefinitionReferenceIds": "installEndpointProtection",
"ExemptionCategory": "Waiver",
"DisplayName": "root-mg - Azure Security Benchmark",
"Description": "currently use sophos and not defender",
"ExpiresOn": null,
"Metadata": ""
},
"SystemData": null,
"Name": "345",
"ResourceId": "/providers/Microsoft.Management/managementGroups/root-mg/providers/Microsoft.Authorization/policyExemptions/345",
"ResourceName": "345",
"ResourceGroupName": null,
"ResourceType": "Microsoft.Authorization/policyExemptions",
"SubscriptionId": null
}
]
Finally, I don't appear to get all Exemptions back in this loop i.e. some are set at Resource Group or Resource Level. Do I need to drill further beyond Set-AzContext?
After reproducing the same code from my end, I could able to see the expected results. However, make sure you are checking in the right file and the location to which you are sending your data to.
Finally, I don't appear to get all Exemptions back in this loop i.e. some are set at Resource Group or Resource Level.
This might be due to the scope that you are looking into. After setting the scope to the required level I could able to get the expected results. Below is the code that worked for me.
$Resource = Get-AzResource -ResourceGroupName <YOUR_RESOURCEGROUP_NAME>
for($I=0;$I -lt $Resource.ResourceId.Count;$I++)
{
$a=Get-AzPolicyExemption -Scope $Resource.ResourceId[$I]
for($J=0;$J -lt $a.Count;$J++)
{
If($a.ResourceId[$J] -Match $Resource.ResourceId[$I])
{
$exemptionsIn = Get-AzPolicyExemption -Scope $Resource.ResourceId[$I] | ConvertTo-Json
$fileName = "sample2" + ".json"
$exemptionsOut = ''
foreach($ex in $exemptionsIn|ConvertFrom-Json){
if($ex.Properties.PolicyAssignmentId -Match "Swetha*") {
$exemptionsOut += $ex|ConvertTo-Json
}
}
if ($exemptionsOut -ne '') {
$exemptionsOut | Out-File -filepath $fileName
$exemptionsOut = ''
}
}
}
}
I have few policy exemptions in my subscription but above script gave me the results at Resource level which -Match with Swetha.
RESULTS:
I am trying to populate a google sheet using an API. But the API has more than one row to be returned for a single query. Following is the JSON returned by API.
# https://api.dictionaryapi.dev/api/v2/entries/en/ABANDON
[
{
"word": "abandon",
"phonetics": [
{
"text": "/əˈbændən/",
"audio": "https://lex-audio.useremarkable.com/mp3/abandon_us_1.mp3"
}
],
"meanings": [
{
"partOfSpeech": "transitive verb",
"definitions": [
{
"definition": "Cease to support or look after (someone); desert.",
"example": "her natural mother had abandoned her at an early age",
"synonyms": [
"desert",
"leave",
"leave high and dry",
"turn one's back on",
"cast aside",
"break with",
"break up with"
]
},
{
"definition": "Give up completely (a course of action, a practice, or a way of thinking)",
"example": "he had clearly abandoned all pretense of trying to succeed",
"synonyms": [
"renounce",
"relinquish",
"dispense with",
"forswear",
"disclaim",
"disown",
"disavow",
"discard",
"wash one's hands of"
]
},
{
"definition": "Allow oneself to indulge in (a desire or impulse)",
"example": "they abandoned themselves to despair",
"synonyms": [
"indulge in",
"give way to",
"give oneself up to",
"yield to",
"lose oneself in",
"lose oneself to"
]
}
]
},
{
"partOfSpeech": "noun",
"definitions": [
{
"definition": "Complete lack of inhibition or restraint.",
"example": "she sings and sways with total abandon",
"synonyms": [
"uninhibitedness",
"recklessness",
"lack of restraint",
"lack of inhibition",
"unruliness",
"wildness",
"impulsiveness",
"impetuosity",
"immoderation",
"wantonness"
]
}
]
}
]
}
]
By using the following calls via IMPORTJSON,
=ImportJSON(CONCATENATE("https://api.dictionaryapi.dev/api/v2/entries/en/"&$A2), "/phonetics/text", "noHeaders")
=ImportJSON(CONCATENATE("https://api.dictionaryapi.dev/api/v2/entries/en/"&$A2), "/meanings/partOfSpeech", "noHeaders")
=ImportJSON(CONCATENATE("https://api.dictionaryapi.dev/api/v2/entries/en/"&$A2), "/meanings/definitions/definition", "noHeaders")
=ImportJSON(CONCATENATE("https://api.dictionaryapi.dev/api/v2/entries/en/"&$A2), "/meanings/definitions/synonyms", "noHeaders")
=ImportJSON(CONCATENATE("https://api.dictionaryapi.dev/api/v2/entries/en/"&$A2), "/meanings/definitions/example", "noHeaders")
I am able to get the following in GoogleSheets,
Whereas, the actual output according to JSON should be,
As you can see a complete row is being overwritten. How can this be fixed?
EDIT
Following is the link to sheet for viewing only.
I believe your goal as follows.
You want to achieve the bottom image in your question on Google Spreadsheet.
Unfortunately, I couldn't find the method for directly retrieving the bottom image using ImportJson. So in this answer, I would like to propose a sample script for retrieving the values you expect using Google Apps Script. I thought that creating a sample script for directly achieving your goal might be simpler rather than modifying ImportJson.
Sample script:
function SAMPLE(url) {
var res = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
if (res.getResponseCode() != 200) return res.getContentText();
var obj = JSON.parse(res.getContentText());
var values = obj[0].meanings.reduce((ar, {partOfSpeech, definitions}, i) => {
definitions.forEach(({definition, example, synonyms}, j) => {
var v = [definition, Array.isArray(synonyms) ? synonyms.join(",") : synonyms, example];
var phonetics = obj[0].phonetics[i];
ar.push(j == 0 ? [(phonetics ? phonetics.text : ""), partOfSpeech, ...v] : ["", "", ...v]);
});
return ar;
}, []);
return values;
}
When you use this script, please put =SAMPLE(CONCATENATE("https://api.dictionaryapi.dev/api/v2/entries/en/"&$A2)) to a cell as the custom formula.
Result:
When above script is used, the following
Note:
In this sample script, when the structure of the JSON object is changed, it might not be able to be used. So please be careful this.
References:
Class UrlFetchApp
Custom Functions in Google Sheets
New to Karate, and JSON, for that matter, but I've got a variable like:
response {
entries {
products [
{
names [
"Peter Parker",
"Tony Stark",
"Captain America"
]
},
{
names [
"Thomas Tinker",
"Jimmy Johnson",
"Mama Martha"
]
}
]
}
}
match each response.entries.products[*].names returns a list like:
["Peter Parker","Tony Stark","Captain America","Thomas Tinker","Jimmy Johnson","Mama Martha"]
But I'd like to assign that output to a variable, such as:
* def variable = response.entries.products[*].names
that would hold a similar value. When I use the above line, I get the following error:
Expected an operand but found *
Is it possible to achieve that, or something similar? If so, how?
Thanks!
Yes, there is syntax for that:
* def variable = $response.entries.products[*].names
Read the docs: https://github.com/intuit/karate#get
My payload looks like this :
{
"override_source": "DS",
"property_code": "0078099",
"stay_date": "2018-11-26T00:00:00.000000",
"sku_prices": [
],
"persistent_override": false
}
There is an array dblist ["2","3"] , it would consists of numbers from 1 to 4. Based on the elements present in the list, I want to add key-values {"sku_price":"1500","sku_code":"2"} to my payload. I am using the following code :
* eval if(contains("3",dblist)) karate.set('pushRatesFromDS.sku_prices[]','{ "sku_price": "1500","sku_code":"3" }')
When I execute my feature file, I do not get any errors but, key-values are not added to my payload. However if I move this code to a new feature file and call it, key-value pairs get added to my payload. The code in my new feature file looks like : * set pushRatesFromDS.sku_prices[] = { "sku_price": "1500","sku_code":"2" }
Try this:
* def foo =
"""
{
"override_source": "DS",
"property_code": "0078099",
"stay_date": "2018-11-26T00:00:00.000000",
"sku_prices": [
],
"persistent_override": false
}
"""
* eval karate.set('foo', '$.sku_prices[]', { foo: 'bar' })
I have used Node to retrieve a set of results from SQL and they're returned like this;
[
{
"event_id": 111111,
"date_time": "2012-11-16T01:59:07.000Z",
"agent_addr": "127.0.0.1",
"priority": 6,
"message": "aaaaaaaaa",
"up_time": 9015040,
"hostname": "bbbbbbb",
"context": "ccccccc"
},
{
"event_id": 111112,
"date_time": "2012-11-16T01:59:07.000Z",
"agent_addr": "127.0.0.1",
"priority": 6,
"message": "aaaaaaaaa",
"up_time": 9015040,
"hostname": "bbbbbbb",
"context": "ddddddd"
},
]
There are usually a lot of entries in the array and I need to efficiently filter the array to show only the entries that have a context of "ccccccc". I've tried a for loop, but it's incredibly slow.
Any suggesstions?
There is a very simple way of doing that if you want to do that in node and don't want to use sql for that you can user javascript built-in Array.filter function.
var output = arr.filter(function(x){return x.context=="ccccccc"}); //arr here is you result array
The ouput array will contains only objects having context "ccccccc".
Another way of doing what Khurrum said, is with the arrow function. It has the same result but some people prefer that notation.
var output = arr.filter(x => x.context == "ccccccc" );
As suggested by Matt, why not include WHERE context = "ccccccc" in yout SQL query?
Else if you must keep all in maybe use one of the following to filter the results
// Place all "ccccccc" context row in an array
var ccccccc = [];
for (var i = results.length - 1; i >= 0; i--) {
if(results[i] == 'ccccccc')
ccccccc.push(results[i]);
};
// Place any context in an named array within an object
var contexts = {};
for (var i = results.length - 1; i >= 0; i--) {
if(contexts[results[i]] == 'undefined')
contexts[results[i]]
contexts[results[i]].push(results[i]);
};
or use the underscore (or similar) filter function.