1

I am using curl(7.39.0) to call a REST webservice and write the response to a .json file.

Here's how I call it

curl -L-X POST -b cookies.txt -H "Content-Type: application/json" http://localhost:7001/web/service/url -d {"param1":"value1"} -o "C:\output\serviceName.json" 

The response is written to the output file, but, without formatting.

{"status": "success","user": "name", "regId": "14420","subscriber": [{"memberFor":"3 years","lastLogin":"2 days ago"}]}

My questions are:

a/ Is there a way to format the json response before writing it to the output file(like below)?

{
        "status": "success",
        "user": "name",
        "regId": "14420"
        "subscriber": [
            {
                "memberFor":"3 years",
                "lastLogin":"2 days ago"     
            }
        ]
    }

b/ If formatting is not possible via cURL, I wish to write a simple batch file to automatically open the json output file and apply formatting. Something like

@echo off
cls
"C:\Program Files\Notepad++\notepad++"   "C:\output\serviceName.json" 
pause

Are there any flags/options available in MS-BATCH to achieve this?

Thanks

BiscuitBoy
  • 311
  • 3
  • 13
  • cURL is basically a *copy* utility so it downloads the file as is. The second part of the question has many googlable duplicate questions on stackoverflow with various solutions. – wOxxOm Nov 19 '15 at 10:10
  • @wOxxOm - Thanks! I found out that it is possible using python but I am unable to find the right syntax. I am using `"filename.json" | python -m json.tool` but I am getting - No JSON object can be decoded. Can you please point me to the right syntax? – BiscuitBoy Nov 19 '15 at 12:07
  • @BiscuitBoy maybe try `python -m json.tool < filename.json`. If that doesn't work, you could try `type filename.json | python -m json.tool`. – rojo Nov 19 '15 at 18:01
  • @rojo - both the syntaxes work and format the json response in the cmd window itself. It doesn't format the JSON file that is written as output. However, I will try your suggested solutions and mark as accepted. – BiscuitBoy Nov 20 '15 at 06:44
  • If you decide you prefer the python beautifier after all, you ought to be able to redirect the output into a file. `python -m json.tool < filename.json > beautified.json` – rojo Nov 20 '15 at 13:04
  • That works! But it creates two json files, one formatted and the other non-formatted. Is there a way to avoid the non-formatted file from being created. Just curious. Apologies for pestering but I am having a hard time with Batch syntax as a beginner. – BiscuitBoy Nov 21 '15 at 05:33
  • 1
    You could try `python -m json.tool < filename.json > filename.json` but I'm not sure whether that'll successfully overwrite or give you a file-in-use error. If error, then `python -m json.tool < filename.json > filename.tmp && move /y filename.tmp filename.json`. – rojo Nov 23 '15 at 02:22

3 Answers3

1

Edit: I found a solution using the htmlfile COM object, which should offer the fastest performance (at least for a single run) and does not require an Internet connection. See the last solution in this answer.


Because you tagged this question with the [batch-file] tag, and because I found the challenge interesting, I wrote a hybrid batch + JScript script that'll beautify your JSON. Since JScript 5.7 doesn't natively support the JSON object, this script uses an external json2.js, downloading it via XHR if it's not already been downloaded. From there, it's a simple matter of calling JavaScript's familiar JSON.stringify() method with its beautify options.

Syntax:

json_generator | batfile.bat
    -or-
batfile.bat < jsonfile.json

Example usage:

beautify.bat < "C:\output\serviceName.json" > "C:\output\beautified.json"

This results in the following being saved as beautified.json:

{  
        "status": "success",
        "user": "name",
        "regId": "14420",
        "subscriber": [
                {
                        "memberFor": "3 years",
                        "lastLogin": "2 days ago"
                }
        ]
}

The code:

@if (@CodeSection == @Batch) @then

@echo off & setlocal

cscript /nologo /e:JScript "%~f0"
goto :EOF

@end // end Batch / begin JScript hybrid chimera

var xObj = WSH.CreateObject('Microsoft.XMLHTTP'),
    fso = WSH.CreateObject('Scripting.FileSystemObject'),
    temp = WSH.CreateObject('WScript.Shell').Environment('Process')('temp'),
    j2lib = 'https://raw.githubusercontent.com/douglascrockford/JSON-js/master/json2.js',
    json = WSH.StdIn.ReadAll();

if (fso.FileExists(temp + '\\json2.js')) {
    j2lib = fso.OpenTextFile(temp + '\\json2.js', 1);
    eval(j2lib.ReadAll());
    j2lib.Close();
}
else {
    with (xObj) {
        open("GET", j2lib, true);
        setRequestHeader('User-Agent', 'XMLHTTP/1.0');
        send('');
    }

    while (xObj.readyState != 4) WSH.Sleep(50);
    eval(xObj.responseText);
    j2lib = fso.CreateTextFile(temp + '\\json2.js', true);
    j2lib.Write(xObj.responseText);
    j2lib.Close();
}

WSH.Echo(JSON.stringify(JSON.parse(json), null, '\t'));

Here's another solution using the same syntax that does not require downloading json2.js. It avoids this by launching Internet Explorer invisibly, calling IE's built-in JSON methods, then silently closing IE again. This is most likely going to be slower than the method above, and it could be blocked depending on machine security policies; but it does have the advantage of working without an Internet connection.

@if (@CodeSection == @Batch) @then

@echo off & setlocal

cscript /nologo /e:JScript "%~f0"
goto :EOF

@end // end Batch / begin JScript hybrid chimera

var IE = WSH.CreateObject('InternetExplorer.Application'),
    json = WSH.StdIn.ReadAll();

IE.Visible = 0;
IE.Navigate('about:blank');
while (IE.Busy || IE.ReadyState != 4) WSH.Sleep(25);

var JSON = IE.document.parentWindow.JSON,
    pretty = JSON.stringify(JSON.parse(json), null, "\t");

WSH.Echo(pretty);

IE.Quit();
try { while (IE && IE.Busy) WSH.Sleep(25); }
catch(e) {}

Here's one more solution, this time using a batch / HTA hybrid. There's a <meta> tag forcing the HTA interpreter into IE9 compatibility, thus including support for JSON methods. This is faster than the IE method, but isn't completely invisible. The HTA window flashes on the screen for an instant, then closes itself.

<!-- : batch portion

@echo off & setlocal

rem // The for /f loop forces mshta to communicate with stdout
rem // as a console script host.  Without for /f, attempting
rem // to write to stdout results in an invalid handle error.
for /f "delims=" %%I in ('mshta.exe "%~f0"') do echo(%%I
goto :EOF

end batch / begin HTA : -->

<meta http-equiv="x-ua-compatible" content="IE=9" />
<script>
var fso = new ActiveXObject('Scripting.FileSystemObject'),
    stdin = fso.GetStandardStream(0),
    stdout = fso.GetStandardStream(1),
    json = stdin.ReadAll(),
    pretty = JSON.stringify(JSON.parse(json), null, '\t');

close(stdout.Write(pretty));
</script>

The best solution I think is to use the scantily-documented htmlfile COM object. Using the same trick with a <meta> tag to force it into IE9 compatibility as was demonstrated with the HTA solution above, the htmlfile COM object offers native support for JSON methods without requiring a library download, and without forking an additional windowed helper application. It just loads a dll.

@if (@CodeSection == @Batch) @then

@echo off & setlocal

cscript /nologo /e:JScript "%~f0"
goto :EOF

@end // end batch / begin JScript hybrid chimera

var htmlfile = WSH.CreateObject('htmlfile'),
    json = WSH.StdIn.ReadAll();

htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=9" />');

var JSON = htmlfile.parentWindow.JSON,
    pretty = JSON.stringify(JSON.parse(json), null, '\t');

htmlfile.close(WSH.Echo(pretty));
Community
  • 1
  • 1
rojo
  • 22,626
  • 5
  • 47
  • 93
  • WOW! @rojo - I didn't know there could be so many ways to make this work. I will try them all, especially, the last solution involving the `htmlfile` CON object. Thank you very much! – BiscuitBoy Nov 20 '15 at 06:41
  • Well, actually, the first solution, the one that downloads json2.js, is the fastest after json2.js has been downloaded. But the last one is the fastest that doesn't require an Internet connection. – rojo Nov 20 '15 at 12:54
  • @BiscuitBoy Just curious, what is it you intend to do with the JSON data after it's been beautified? If your intention is to scrape the data using text manipulation, you should re-evaluate your situation. These JSON methods I'm suggesting all put the data into an object, then serialize it into a beautified string again. You can cut out the middle man by doing something like `var sessData = JSON.parse(json);` then `WSH.Echo('Last login: ' + sessData.subscriber[0].lastLogin);` or similar. – rojo Nov 20 '15 at 16:44
  • **@rojo** -I am creating a batch file that makes use of cURL to call multiple web services that require a set of input params. My aim is to configure the URLs and inputs separately, call cURL for each configured URL and input , prettify and write the response to a json file, place the output files in a directory with a time-stamp. This directory wil be accessed by a testing team, who have their own plugin to verify test cases/ WS responses. At present , we are using some browser extensions to call each WS, save the JSON response, place all the responses in a directory, all done manually. – BiscuitBoy Nov 21 '15 at 05:43
  • @BiscuitBoy If my answer was helpful, please consider marking it as accepted, or at least upvoting it. [See this page](http://meta.stackexchange.com/questions/5234/) for an explanation of why this is important. – rojo Nov 23 '15 at 02:19
  • Upvoted now.Earlier, I didn't have the upvote privilege. Without a doubt, your answers were very helpful! They taught me tricks of BATCH programming that I couldn't learn from online docs. I will mark your answer as accepted once I try them out. Currently, I have some other priorities which I am working on. So please bear with me. I reckon there is no time-frame to mark-as-accepted. Thank you – BiscuitBoy Nov 23 '15 at 05:35
  • All your solutions taught me a thing or two (more than that, actually) about Batch scripting.But personally, I preferred using Python to format the output. Then again , you pointed me to the right syntax in one of your comments above. Hence, I mark your answer as accepted. Mighthy thanks for your help, @rojo :) – BiscuitBoy Nov 23 '15 at 06:20
  • Hi rojo, I asked a similar question [here](https://stackoverflow.com/q/65207848/4999991). Would appreciate it if you could take look at that one as well. Thanks in advance. – Foad Dec 08 '20 at 22:30
  • 1
    @Foad That's a really good question. I noticed you left out the `x-ua-compatible` meta tag to force the `htmlfile` COM object into IE9 (or higher) compatibility, as well as cloning the method into the current runspace from `htmlfileObj.parentWindow`. But adding those missing elements doesn't really fix it either. I can find no way through this `htmlfile` object hack to expose `Object.getOwnPropertyDescriptor()` in a working fashion. Attempting to use it makes the script silently die, and `try` / `catch` catches a vague type error, like the thing you're trying to get the Property Descriptor... – rojo Dec 11 '20 at 19:06
  • ... of isn't a standard object. What's especially weird is that I have [used this method successfully](https://stackoverflow.com/a/34966337/1683264) to clone other object methods. In any case, forcing the Chakra engine at the command line using `cscript //E:{1b7cd997-e5ff-4932-a7a6-2a9e636da385}` as [Kul-Tigin suggests](https://stackoverflow.com/a/65216915/1683264) is probably the only solution. – rojo Dec 11 '20 at 19:10
0

Just an update for oldish question. https://stedolan.github.io/jq/ is pretty nice tool. By default, jq pretty-prints JSON output. So you can do

curl ... | jq .

"." is identity filter. See https://stedolan.github.io/jq/manual/#Invokingjq

ljack
  • 53
  • 2
  • 6
  • 1
    While this link may answer the question, link only answers are discouraged on Stack Overflow, you can improve this answer by taking vital parts of the link and putting it into your answer, this makes sure your answer is still an answer if the link gets changed or removed :) – WhatsThePoint Aug 02 '17 at 06:57
0

I believe this can all be done with Xidel, which prettyprints by default:

xidel -s --method=POST ^
--load-cookies="cookies.txt" ^
-H "Content-Type: application/json" ^
-d {"param1":"value1"} ^
http://localhost:7001/web/service/url ^
-e "$json"

Expected output:

{
  "status": "success",
  "user": "name",
  "regId": "14420"
  "subscriber": [
    {
      "memberFor":"3 years",
      "lastLogin":"2 days ago"     
    }
  ]
}
Reino
  • 1,704
  • 9
  • 15