1

New to learning powershell here, open to any solution really as long as its self contained within the given script

It's really hard finding anything in the docs about this, which maybe means there isn't anything?

PSVersion 5.1

Build Version: 10.0.17134

No particular JSON standard or file setup will be known, but we can use this for an example:

{
  "foo" : ["foo1", "foo2", "foo3"],
  "bar" : {
     "bar-foo" : 20,
     "bar-bar" : {
       "bar-bar-foo : "here"
     }
   }
}

I would like to simply navigate to a JSON KVP directly, as I will know the property before hand, and it's full nested path.

For example, input argument would be:

.\ScriptName -JsonProp ["bar"]["bar-bar"]["bar-bar-foo"]

My script is currently using ConvertTo-Json and the source is any given .json file, but if there is an easier or more friendly way to explicitly navigate a json object I'm all ears.

Goal is to change the value of that property and write it out to file

$json = Get-Content -Raw -Path $path | ConvertFrom-Json

$json.$JsonProp = "there"

As far as I can tell, the Json object has to use dot notation, but I've tried that with strings with no resolve, splitting the parameter for example:

.\ScriptName -JsonProp bar.bar-bar.bar-bar-foo

$json.$JsonProp = "there"

While I'm on the topic if you have any recommendations for literature on powershell please let me know.

3 Answers 3

2

Any identified in your JSON object that contains a non-alpha character needs to be quoted:

$rawJson = @"
{
  "foo": ["foo1", "foo2", "foo3"],
  "bar": {
     "bar-foo": 20,
     "bar-bar": {
       "bar-bar-foo" : "here"
     }
  }
}
"@

$json = ConvertFrom-Json -InputObject $rawJson

# Note the quotes!!
$json.bar."bar-bar"."bar-bar-foo" = "Hello thar!"

$json | ConvertTo-Json

Result

{
    "foo":  [
                "foo1",
                "foo2",
                "foo3"
            ],
    "bar":  {
                "bar-foo":  20,
                "bar-bar":  {
                                "bar-bar-foo":  "Hello thar!"
                            }
            }
}

Using "Dymanic" JSON path

IMPORTANT DISCLAIMER: this code is dangerous. You can pass all sorts in to the $jsonPath variable and it will be executed. It's akin to SQL injection and you need to be very careful.

Obligatory xkcd

$rawJson = @"
{
  "foo": ["foo1", "foo2", "foo3"],
  "bar": {
     "bar-foo": 20,
     "bar-bar": {
       "bar-bar-foo" : "here"
     }
  }
}
"@

$jsonPath = 'bar."bar-bar"."bar-bar-foo"'
$newValue = "Hello thar!"
$command = "`$json.$jsonPath = '$newValue'"

$json = ConvertFrom-Json -InputObject $rawJson

Invoke-Expression $command

$json | ConvertTo-Json
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for the swift response, however, this works by simply typing it in, my issue is passing a command line argument to the $json object as it's full property path, unless I'm just oblivious
@soulshined added an extra snippet for performing a dynamic assigment using a $jsonPath variable.
@soulshined please read the disclaimer I have added above. Try run it with $jsonPath = 'bar."bar-bar"."bar-bar-foo"; Write-Host "Scary code..."; #' to see the problem in action. That Write-Host could be anything - so be careful.
Someone beat you to it :( Thank you though. I really appreciate it. I'm currently now learning about Invoke-Expression and how to prevent real commands from being called :P
1

Here's one way:

$newValue = "there"
Invoke-Expression ('$json.' + $JsonProp + ' = $newValue')

You would have to include the quotes in the argument:

.\ScriptName -JsonProp "bar.'bar-bar'.'bar-bar-foo'"

To prevent typos or security issues you could / should verify the argument beforehand, for instance using a simple regex check:

# (Note: This is just a quick example. Not pretty or complete, but does the job.)
if ($JsonProp -notmatch '^([a-zA-Z0-9_\-]+|''[a-zA-Z0-9_\-"]+''|"[a-zA-Z0-9_\-'']+")(\.([a-zA-Z0-9_\-]+|''[a-zA-Z0-9_\-"]+''|"[a-zA-Z0-9_\-'']+"))*$') {
    throw "Invalid json property."
}

6 Comments

Fantastic. +1 for making me look up Invoke-Expression
@soulshined It's useful but also dangerous, so be careful.
@soulshined No. It will simply treat strings as commands. This can be dangerous because arbitrary code could be passed as argument to your script. So make sure to take appropriate precautions if necessary.
"Take reasonable precautions when using the Invoke-Expression cmdlet in scripts. When using Invoke-Expression to run a command that the user enters, verify that the command is safe to run before running it." Yeah I see, I might not end up using this as I will be sharing this script with others and I'm not concerned about them intentionally harming their own computer, more concerned with typos or indirect damage. But it did get the job done! No complaints
Sorry, last question @marsze So I'm sure the issue is injection right? Someone could potentially enter more powershell commands along with the parameter. But if that command is wrapped around a $json variable it would be kind of hard for it execute unless they go in and change the code themselves. This script will simply be for personal user use, so it should be fine right?
|
1

if there is an easier or more friendly way to explicitly navigate a json object I'm all ears

Yes there are plenty... Here is an example using jq JSON command line parser

Provided your JSON sample, to change the value here to there, you would simply do:

jq '.bar."bar-bar"."bar-bar-foo" |= "there"' file
{
  "foo": [
    "foo1",
    "foo2",
    "foo3"
  ],
  "bar": {
    "bar-foo": 20,
    "bar-bar": {
      "bar-bar-foo": "there"
    }
  }
}

In jq, the assignment operator is |=.

It's not always mandatory to double quote JSON keys, but here it is to avoid confusion with the subtraction operator.

2 Comments

I upvoted, thanks for a quick response. I think I should not have used the example I used, I just made it up on the fly, I guess the real problem isn't the key, it's passing it in from command line as an argument and then doing something like $json.$<full property path from parameter>
@soulshined Then you need this: jq --arg v "there" '.bar."bar-bar"."bar-bar-foo" |= $v' file

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.