20

How do you parse a simple JSON string in Batch?

For example, if I have the following JSON string:

{ "...":"...", "year": 2016, "time": "05:01", "...":"...", }

This JSON string has a number of elements and two of them are "year" and "time". From this string, I want to extract the values for "year" and "time" in two variables without using any external libraries (i.e., I don't want to install download any external utils for this, a freshly installed Windows should have all the necessary tools to do this).

Andrei
  • 7,509
  • 7
  • 32
  • 63
  • 4
    Out of morbid curiosity, why batch? JScript or PowerShell would be simpler for this - both have native support for JSON. – Mark Reed Apr 02 '16 at 14:28
  • 2
    Ok, how would this look in PowerShell? given the JSON file, return the values for year and time in a batch script. – Andrei Apr 02 '16 at 15:24
  • 2
    @MarkReed: I would like to see such "simpler" (although much slower) PowerShell solution... – Aacini Apr 02 '16 at 16:06
  • 2
    You can lose the scare-quotes, @Aacini. Using bespoke string manipulation on the fly to parse JSON - or any other structured format - is almost always going to be the wrong solution compared to an existing, tested library specifically designed to parse that format. And if you're writing in any of these languages, execution speed is not your priority. See http://stackoverflow.com/questions/16575419/powershell-retrieve-json-object-by-field-value. Getting a useful PowerShell object out of a JSON string is a one-liner: `$string | ConvertFrom-Json`. I'd say that's "simpler". – Mark Reed Apr 02 '16 at 21:25
  • 1
    @MarkReed I emphatically agree that deserializing JSON as an object is much better than scraping JSON as flat text. Unfortunately, the `ConvertFrom-Json` is only available in PowerShell 3.0+. Most Win 7 boxes (any that I've used, anyway) default to PowerShell 2.0, which makes deployment of a script using that cmdlet problematic for many end users. Still, creating a `System.Web.Script.Serialization.JavaScriptSerializer` object only requires [a couple additional lines](http://stackoverflow.com/a/36378904/1683264). – rojo Apr 02 '16 at 21:52
  • @MarkReed: This is funny! Every people that had posted that _"PowerShell is simpler for this than Batch"_ had _never_ proved such point posting the "simpler" PS solution they are talking about! They all prefer to post _comments_ against Batch and in favour of PowerShell, but they never posted working PS code! My Batch file solution below uses 4 `set` commands to parse all values from the JSON string into Batch variables, so a simpler PowerShell solution should take less than 4 short lines, unless "simpler" have a different meaning for you... – Aacini Apr 02 '16 at 23:58
  • @Aacini I feel ya. The "I don't know how to solve this in batch, so you need to learn a different language" comments get old, and they appear at unreasonable and ludicrous frequency. In this case, though, I can't disagree with Mark Reed that JSON (and XML, HTML, and any other type of structured data) is best parsed as hierarchical objects rather than as flat text. What if one day the JSON suddently had no space around the brace characters, or after the colons? It's still valid JSON, but it'd break a script that relies on text scraping (unless that script uses uncommonly clever regexps). – rojo Apr 03 '16 at 01:21
  • @rojo: Of course, don't misunderstand me! I completely agree that the right way to process JSON strings and other types of structured data is _not_ via a Batch file, but via the native support of modern programming languages. What I said is that doing that is certainly _not_ simpler than a rudimentary "solution" in Batch, as PS fans frequently stated. My advice is: if a _simple and limited_ problem can be solved via a Batch file, just stay with it; otherwise, you must complete an additional research in order to create a better solution via PS or JScript. – Aacini Apr 03 '16 at 04:01

5 Answers5

19

Here's a hybrid Batch + PowerShell solution. The PowerShell objectifies (deserializes) the JSON text and outputs it in key=value format to be captured and set as batch variables by the batch for /f loop. Save this with a .bat extension and give it a shot.

<# : batch portion (contained within a PowerShell multi-line comment)
@echo off & setlocal

set "JSON={ "year": 2016, "time": "05:01" }"

rem # re-eval self with PowerShell and capture results
for /f "delims=" %%I in ('powershell "iex (${%~f0} | out-string)"') do set "%%~I"

rem # output captured results
set JSON[

rem # end main runtime
goto :EOF

: end batch / begin PowerShell hybrid code #>

add-type -AssemblyName System.Web.Extensions
$JSON = new-object Web.Script.Serialization.JavaScriptSerializer
$obj = $JSON.DeserializeObject($env:JSON)

# output object in key=value format to be captured by Batch "for /f" loop
foreach ($key in $obj.keys) { "JSON[{0}]={1}" -f $key, $obj[$key] }

Then if you want only the year and time values, just use %JSON[year]% or %JSON[time]%.

If you're reading your JSON from a .json file, you could have the PowerShell portion read the file, replacing ($env:JSON) with ((gc jsonfile.json)). Then you wouldn't be dependent at all on whether your JSON is multi-line and beautified or minified. It'll be deserialized all the same either way.


If execution speed is a concern, you might prefer a Batch + JScript hybrid solution. It deserializes the JSON into an object the same as the PowerShell solution, but invoking JScript from a Batch context is faster than invoking a PowerShell command or script from Batch.

@if (@CodeSection == @Batch) @then
@echo off & setlocal

set "JSON={ "year": 2016, "time": "05:01" }"

rem // re-eval self with JScript interpreter and capture results
for /f "delims=" %%I in ('cscript /nologo /e:JScript "%~f0"') do set "%%~I"

rem // output captured results
set JSON[

rem // end main runtime
goto :EOF

@end // end Batch / begin JScript hybrid code

var htmlfile = WSH.CreateObject('htmlfile'),
    txt = WSH.CreateObject('Wscript.Shell').Environment('process').Item('JSON');

htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=9" />');
var obj = htmlfile.parentWindow.JSON.parse(txt);
htmlfile.close();

for (var i in obj) WSH.Echo('JSON[' + i + ']=' + obj[i]);

And as is the case with the first PowerShell hybrid solution, you can parse multi-line JSON by reading the .json file from within the JScript portion if you wish (by creating a Scripting.FileSystemObject object and calling its .OpenTextFile() and .ReadAll() methods).


Here's another pure batch solution, but one which sets key=value pairs as an associative array to avoid stomping on %time% as Aacini's solution does. I really think it's better to parse JSON as an object in a helper language rather than as flat text in pure batch, but I also realize that the best answer is not always the most popular.

@echo off
setlocal

set "JSON={ "other": 1234, "year": 2016, "value": "str", "time": "05:01" }"

set "JSON=%JSON:~1,-1%"
set "JSON=%JSON:":=",%"

set mod=0
for %%I in (%JSON%) do (
    set /a mod = !mod
    setlocal enabledelayedexpansion
    if !mod! equ 0 (
        for %%# in ("!var!") do endlocal & set "JSON[%%~#]=%%~I"
    ) else (
        endlocal & set "var=%%~I"
    )
)

set JSON[
rojo
  • 24,000
  • 5
  • 55
  • 101
18
@echo off
setlocal

set string={ "other": 1234, "year": 2016, "value": "str", "time": "05:01" }

rem Remove quotes
set string=%string:"=%
rem Remove braces
set "string=%string:~2,-2%"
rem Change colon+space by equal-sign
set "string=%string:: ==%"
rem Separate parts at comma into individual assignments
set "%string:, =" & set "%"

echo other="%other%"
echo year="%year%"
echo value="%value%"
echo time="%time%"

Output:

other="1234"
year="2016"
value="str"
time="05:01"

A small inconvenient of this method is that "time" Batch dynamic variable will be replaced by the new JSON one, but if you use setlocal command, this point don't cause any problem.

EDIT: Just to complete cz3ch's request of put results into "string" array instead of placing them inside individual variables:

@echo off
setlocal

set string={ "other": 1234, "year": 2016, "value": "str", "time": "05:01" }

rem Remove quotes
set string=%string:"=%
rem Remove braces
set "string=%string:~2,-2%"
rem Change colon+space by "]equal-sign"
set "string=%string:: =]=%"
rem Separate parts at comma into individual array assignments
set "string[%string:, =" & set "string[%"

set string[
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • Good simple answer! – cascading-style Nov 24 '16 at 21:40
  • Great solution ! How could we place them inside an array instead of placing them inside individual variable for exemple string[time] – cz3ch Aug 31 '18 at 13:58
  • Used this solution but updated a little bit since my json does not have spaces: remove braces `set "string=%string:~1,-1%"` also have removed spaces from `set "string=%string::==%"` and `set "%string:,=" & set "%"`. This should be an answer since does not require additional tools (like **poweshell** or **jq**) – oleksa Dec 21 '20 at 09:31
5

There are different ways, here's one.

@echo off
set string={ "year": 2016, "time": "05:01" }
set string=%string:"=%

for /f "tokens=3,5" %%a in ('echo %string%') do set d=%%a&set t=%%b
echo -%d%- -%t%-
pause & goto :EOF

and here's a second:

@echo off
set string={ "year": 2016, "time": "05:01" }

for /f "tokens=3,5" %%a in ('echo %string%') do set d=%%~a&set t=%%~b
echo -%d%- -%t%-
pause & goto :EOF
foxidrive
  • 40,353
  • 10
  • 53
  • 68
3

Use https://stedolan.github.io/jq/, no need to use powershell or string manipulations in batch.

Example to get value of field from json string:

set "year={ "other": 1234, "year": 2016, "value": "str", "time": "05:01" } | jq .year"

echo %year%

gives you 2016

Praveen Tiwari
  • 1,200
  • 1
  • 12
  • 25
  • 2
    this is the easiest and most powerful solution. thanks for sharing. Easy to install simply with chocolatey: choco install jq – Igor Lino Jan 31 '22 at 18:28
1

with jsonextractor.bat:

for /f "tokens=* delims=" %%a in ('jsonextractor.bat simple.json year') do set "year=%%~a"

for /f "tokens=* delims=" %%a in ('jsonextractor.bat simple.json time') do set "time_=%%~a"

echo %year% -- %time_%
npocmaka
  • 55,367
  • 18
  • 148
  • 187