jsonpython-3.xpowershellcommand-line-argumentsargparse

How to pass a json as a string argument in python code with use of argsparse and Powershell?


I've been wrapping my head around this problem and can't find a solution.

I have a pretty simple code:

import argparse
import json

def main():
    parser = argparse.ArgumentParser(description="Process a JSON argument.")
    parser.add_argument(
        "--json",
        type=str,
        required=True,
        help="JSON string or path to a JSON file."
    )
    args = parser.parse_args()
    
    # Try to parse the argument as a JSON string or file path
    try:
        # First, attempt to parse it as a JSON string
        data = json.loads(args.json)
        print("Parsed JSON string successfully:", data)
    except json.JSONDecodeError:
        # If that fails, treat it as a file path
        try:
            with open(args.json, 'r') as file:
                data = json.load(file)
                print("Parsed JSON file successfully:", data)
        except FileNotFoundError:
            print("Error: Input is not a valid JSON string or file path.")
            return

    # Use the parsed dictionary (data)
    print("JSON as dictionary:", data)

if __name__ == "__main__":
    main()

saved as script.py When I run it from Powershell with

python script.py --json '{"key": "value", "number": 42}'

I receive:

Traceback (most recent call last):
  File "C:\mypath\script.py", line 17, in main
    data = json.loads(args.json)
  File "C:\Users\myuser\miniconda3\envs\ti_nlp\lib\json\__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "C:\Users\myuser\miniconda3\envs\ti_nlp\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Users\myuser\miniconda3\envs\ti_nlp\lib\json\decoder.py", line 353, in raw_decode
    obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\mypath\script.py", line 33, in <module>
    main()
  File "C:\mypath\script.py", line 22, in main
    with open(args.json, 'r') as file:
OSError: [Errno 22] Invalid argument: '{key: value, number: 42}'

When i run it with cmd (Anaconda prompt):

python script.py --json "{\"key\": \"value\", \"number\": 42}"

it works just fine:

Parsed JSON string successfully: {'key': 'value', 'number': 42}
JSON as dictionary: {'key': 'value', 'number': 42}

So... what is the issue with Powershell and how can I solve this problem?


Solution

  • This is an issue prior to the introduction of PSNativeCommandArgumentPassing, before that the double-quotes are consumed when passed as arguments. For comparison, you can change your Python script to:

    import sys
    
    def main():
        print(sys.argv)
    
    if __name__ == "__main__":
        main()
    

    Then when called from PowerShell 7.2 and below you would see:

    PS ..\pwsh> python script.py --json '{"key": "value", "number": 42}'
    ['script.py', '--json', '{key: value, number: 42}']
    

    And in 7.3+ you will see that the " are preserved:

    PS ..\pwsh> python script.py --json '{"key": "value", "number": 42}'
    ['script.py', '--json', '{"key": "value", "number": 42}']
    

    That's the cause of your issue. To solve this you will need to escape the " with \. A way to handle this dynamically could be via .Replace('"', '\"'):

    $json = '{"key": "value", "number": 42}'
    if ($PSVersionTable.PSVersion -lt '7.3' -or $PSNativeCommandArgumentPassing -eq 'Legacy') {
        $json = $json.Replace('"', '\"')
    }
    python script.py --json $json
    

    There are other alternatives too, for example encoding your Json to Base64 on PowerShell side and decoding it in Python, so in PowerShell:

    $json = '{"key": "value", "number": 42}'
    $b64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($json))
    python script.py --json $b64
    

    And in Python:

    # First, attempt to b64decode and parse it as a JSON string
    data = json.loads(b64decode(args.json))
    

    Then you code should be compatible with all versions.