Search code examples
jsonbatch-filecmd

How to use batch script to replace environment variables inside a JSON file?


I am trying to substitute the environment variables in this following file with their respective values:

some.json:

{
    "verion": 3,
    "name": "some name",
    "description": "some description",
    "api_key": "%API_KEY%"
}

This is the batch script i'm using:

@echo off
setlocal enabledelayedexpansion

set "target_file=some.json"
set "output_file=output.json"
set "API_KEY=12345"

goto:start

:expand
echo %~1 >> %output_file%
goto:eof

:start
echo. > %output_file%
for /f "delims=" %%i in (%target_file%) do call:expand "%%i"

When running this the output.json is exactly:


{ 
    "verion": 3, 
    "api_key": "12345" 
} 

Why is it skipping lines 3 and 4 of some.json? Should it not be outputting all lines of some.json?


Solution

  • Because the contents of the lines have quotes and you're wrapping %%i in quotes, that's actually changing which ones are open quotes and which ones are close quotes. Right now, :expand thinks the data looks like this:

    "{"
    "    "verion": 3,"
    "    "name": "some name","
    "    "description": "some description","
    "    "api_key": "12345""
    "}"
    

    Tokenizing rules get weird at this point, but basically the line gets split on the space after an even number of quotes have happened. This means that for the name line, " "name": "some is the entire first token.

    "    "name": "some
    |    |    | ||    |
    |    |    | ||    This is the first actual space outside of quotes, so we tokenize here
    |    |    | ||
    |    |    | ||
    |    |    | |Now there are an even number of quotes again
    |    |    | |
    |    |    | |
    |    |    | So this space is technically inside of quotes
    |    |    |
    |    |    |
    |    |    There are now three quotes
    |    |
    |    |
    |    This is a close quote but there's no space after it so we keep reading
    |  
    The opening quote
    

    Since the interpreter thinks there's more than one token, you can use %* to get the entire line.

    @echo off
    setlocal enabledelayedexpansion
    
    set "target_file=some.json"
    set "output_file=output.json"
    set "API_KEY=12345"
    
    goto:start
    
    :expand
    >>"%output_file%" echo(%*
    goto:eof
    
    :start
    >"%output_file%" echo(
    for /f "delims=" %%i in (%target_file%) do call :expand %%i