242

I have the following code to let users download data strings in csv file.

exportData = 'data:text/csv;charset=utf-8,';
exportData += 'some csv strings';
encodedUri = encodeURI(exportData);
newWindow = window.open(encodedUri);

It works just fine that if client runs the code it generates blank page and starts downloading the data in csv file.

So I tried to do this with JSON object like

exportData = 'data:text/json;charset=utf-8,';
exportData += escape(JSON.stringify(jsonObject));
encodedUri = encodeURI(exportData);
newWindow = window.open(encodedUri);

But I see only a page with the JSON data displayed on it, not downloading it.

I went through some research and this one claims to work but I don't see any difference to my code.

Am I missing something in my code?

Thanks for reading my question:)

Community
  • 1
  • 1
Eugene Yu
  • 3,708
  • 4
  • 21
  • 27

15 Answers15

411

This is how I solved it for my application:

HTML: <a id="downloadAnchorElem" style="display:none"></a>

JS (pure JS, not jQuery here):

var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(storageObj));
var dlAnchorElem = document.getElementById('downloadAnchorElem');
dlAnchorElem.setAttribute("href",     dataStr     );
dlAnchorElem.setAttribute("download", "scene.json");
dlAnchorElem.click();

In this case, storageObj is the js object you want to store, and "scene.json" is just an example name for the resulting file.

This approach has the following advantages over other proposed ones:

  • No HTML element needs to be clicked
  • Result will be named as you want it
  • no jQuery needed

I needed this behavior without explicit clicking since I want to trigger the download automatically at some point from js.

JS solution (no HTML required):

  function downloadObjectAsJson(exportObj, exportName){
    var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
    var downloadAnchorNode = document.createElement('a');
    downloadAnchorNode.setAttribute("href",     dataStr);
    downloadAnchorNode.setAttribute("download", exportName + ".json");
    document.body.appendChild(downloadAnchorNode); // required for firefox
    downloadAnchorNode.click();
    downloadAnchorNode.remove();
  }
bformet
  • 13,465
  • 1
  • 22
  • 25
volzotan
  • 4,950
  • 1
  • 14
  • 18
  • 4
    This is the only solution that will work for more than =~ 2000 characters of data. Because you prepended data: – rjurney Feb 01 '16 at 21:23
  • 1
    Can somebody point me to a spec or MDN page that explains in more detail how this whole prepended data-type thing works ie. "data:text/json;charset=utf-8"? I am using this, but it feels like magic, would be great to read up on the details but I don't even know how to google for it. – sidewinderguy Jul 25 '16 at 20:02
  • 1
    This doesn't work in IE though when you have to download a big file with more than 2000 characters. – user388969 Feb 16 '17 at 15:38
  • 12
    This will not work without limits, though. You can only download about 1MB of data. For example, `var storageObj = []; for (var i=0; i<1000000; ++i) storageObj.push('aaa');` gives "download Failed - Network error" in Chrome 61 – oseiskar Nov 01 '17 at 12:19
  • An improvement over this answer would be `var dlAnchorElem = document.createElement('a');` instead of `var dlAnchorElem = document.getElementById('downloadAnchorElem');` if your element doesn't exist in DOM. Rest of the code would be same. – Sayed Nov 14 '17 at 17:45
  • Not sure, cause this would happen every time you do a download, which would clutter your DOM with anchor elements... unless you delete it again afterwards. I didn't want to add/remove DOM elements all the time, therefore used a single one throughout. – volzotan Nov 15 '17 at 18:48
  • JSON.stringify force it to store all data in 1 line, how can I simplify/beautify it? – YASH DAVE Feb 05 '18 at 04:24
  • Solution: ```JSON.stringify(exportObj).split(',').join(',\r\n')``` – YASH DAVE Feb 05 '18 at 05:03
  • the above JS solution, if modified to use **dispatchEvent** on the created **a** elemet will be better according to this reply on self-trigger event https://stackoverflow.com/a/46880341/5443164 – Weiting Lin Apr 24 '18 at 01:16
  • 3
    @YASHDAVE use `JSON.stringify(exportObj, null, 2)` instead – TryingToImprove May 08 '18 at 08:43
  • 1
    should add encodeURIComponent. Bcoz my JSON data contains '#' symbol which breaks data – Aravinthan K Jun 05 '19 at 06:15
  • Added code to download large json object using Blob – jwsadler May 07 '20 at 12:54
  • Should use "data:application/octet-stream," instead of json - then the browser will treat it as a binary download. – coolaj86 Jul 15 '21 at 00:52
  • This no longer works, see this answer: https://stackoverflow.com/a/49019549 – zr0gravity7 Jul 28 '21 at 22:04
  • This is still valid for me at 2021-11-18 with Chrome version-96.0.4664.45 – Wei Chen Nov 18 '21 at 09:41
  • @AravinthanK How come '#' is breaking the data? I am facing the same issue, which was resolved when I used base64 encoding. Why does # have special meaning when I used media type plain text? – Sriman Jul 25 '23 at 14:06
45

Found an answer.

var obj = {a: 123, b: "4 5 6"};
var data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(obj));

$('<a href="data:' + data + '" download="data.json">download JSON</a>').appendTo('#container');

seems to work fine for me.

** All credit goes to @cowboy-ben-alman, who is the author of the code above **

Eugene Yu
  • 3,708
  • 4
  • 21
  • 27
  • @Cybear can you explain the third line to me? – Rahul Khatri May 04 '15 at 11:12
  • You will want to prepend data: to your url, otherwise the user is likely to hit a 2000 character limit in many browsers. – rjurney Feb 01 '16 at 21:24
  • Hey , I know it is an old answer but, know this solution doesn't work on IE (all of them) IE does not familiar with download attribute reference - [link](https://stackoverflow.com/questions/18394871/download-attribute-on-a-tag-not-working-in-ie) – Ayalon Grinfeld Aug 10 '17 at 13:35
44

You could try using:

  • the native JavaScript API's Blob constructor and
  • the FileSaver.js saveAs() method

No need to deal with any HTML elements at all.

var data = {
    key: 'value'
};
var fileName = 'myData.json';

// Create a blob of the data
var fileToSave = new Blob([JSON.stringify(data)], {
    type: 'application/json'
});

// Save the file
saveAs(fileToSave, fileName);

If you wanted to pretty print the JSON, per this answer, you could use:

JSON.stringify(data,undefined,2)
erik
  • 150
  • 2
  • 6
Gautham
  • 766
  • 7
  • 15
  • 3
    saveAs() is from FileSaver.js - https://github.com/eligrey/FileSaver.js – Gautham Sep 05 '17 at 08:29
  • 5
    "No need to deal with any HTML elements at all" ... and reading the source code of filesaver.js ... it does exactly that lol – War Sep 24 '17 at 15:55
  • 2
    Yeah. I meant to say, no need to deal with the HTML elements directly. – Gautham Sep 25 '17 at 16:43
  • 3
    This is the best answer since it does not have a 1MB size limit and uses a library instead of custom hacks – oseiskar Nov 01 '17 at 12:41
  • FileSaver.js is very heavy for doing a simple file download. Check my answer down below; CTRL+F and search for "ES6+". – MSOACC Jan 28 '21 at 14:31
34

ES6+ version for 2021; no 1MB limit either:

This is adapted from @maia's version, updated for modern Javascript with the deprecated initMouseEvent replaced by new MouseEvent() and the code generally improved:

const saveTemplateAsFile = (filename, dataObjToWrite) => {
    const blob = new Blob([JSON.stringify(dataObjToWrite)], { type: "text/json" });
    const link = document.createElement("a");

    link.download = filename;
    link.href = window.URL.createObjectURL(blob);
    link.dataset.downloadurl = ["text/json", link.download, link.href].join(":");

    const evt = new MouseEvent("click", {
        view: window,
        bubbles: true,
        cancelable: true,
    });

    link.dispatchEvent(evt);
    link.remove()
};

If you want to pass an object in:

saveTemplateAsFile("filename.json", myDataObj);
Mohammed H
  • 6,880
  • 16
  • 81
  • 127
MSOACC
  • 3,074
  • 2
  • 29
  • 50
30

Simple, clean solution for those who only target modern browsers:

function downloadTextFile(text, name) {
  const a = document.createElement('a');
  const type = name.split(".").pop();
  a.href = URL.createObjectURL( new Blob([text], { type:`text/${type === "txt" ? "plain" : type}` }) );
  a.download = name;
  a.click();
}

downloadTextFile(JSON.stringify(myObj), 'myObj.json');
29

This would be a pure JS version (adapted from cowboy's):

var obj = {a: 123, b: "4 5 6"};
var data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(obj));

var a = document.createElement('a');
a.href = 'data:' + data;
a.download = 'data.json';
a.innerHTML = 'download JSON';

var container = document.getElementById('container');
container.appendChild(a);

http://jsfiddle.net/sz76c083/1

jbcdefg
  • 299
  • 3
  • 3
24

The following worked for me:

/* function to save JSON to file from browser
* adapted from http://bgrins.github.io/devtools-snippets/#console-save
* @param {Object} data -- json object to save
* @param {String} file -- file name to save to 
*/
function saveJSON(data, filename){

    if(!data) {
        console.error('No data')
        return;
    }

    if(!filename) filename = 'console.json'

    if(typeof data === "object"){
        data = JSON.stringify(data, undefined, 4)
    }

    var blob = new Blob([data], {type: 'text/json'}),
        e    = document.createEvent('MouseEvents'),
        a    = document.createElement('a')

    a.download = filename
    a.href = window.URL.createObjectURL(blob)
    a.dataset.downloadurl =  ['text/json', a.download, a.href].join(':')
    e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
    a.dispatchEvent(e)
}

and then to call it like so

saveJSON(myJsonObject, "saved_data.json");
maia
  • 3,910
  • 4
  • 27
  • 34
  • 5
    While this is a great answer, `initMouseEvent()` is a [deprecated](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent) Web Standard and should not be used anymore. Instead, use the [`new MouseEvent()`](https://developer.mozilla.org/en/docs/Web/API/MouseEvent) interface. It's just a minor refactor though. – morkro May 28 '17 at 08:16
  • @morkro is correct; I have posted an answer below that builds and improves on @maia's answer with `initMouseEvent` removed (and the code generally cleaned up) – MSOACC Jan 28 '21 at 14:28
17

I recently had to create a button that would download a json file of all values of a large form. I needed this to work with IE/Edge/Chrome. This is what I did:

function download(text, name, type)
    {
        var file = new Blob([text], {type: type});
        var isIE = /*@cc_on!@*/false || !!document.documentMode;
        if (isIE)
        {
            window.navigator.msSaveOrOpenBlob(file, name);
        }
        else
        {
            var a = document.createElement('a');
            a.href = URL.createObjectURL(file);
            a.download = name;
            a.click();
        }
     }

download(jsonData, 'Form_Data_.json','application/json');

There was one issue with filename and extension in edge but at the time of writing this seemed to be a bug with Edge that is due to be fixed.

Hope this helps someone

Fabian von Ellerts
  • 4,763
  • 40
  • 35
Brad
  • 8,044
  • 10
  • 39
  • 50
7
    downloadJsonFile(data, filename: string){
        // Creating a blob object from non-blob data using the Blob constructor
        const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        // Create a new anchor element
        const a = document.createElement('a');
        a.href = url;
        a.download = filename || 'download';
        a.click();
        a.remove();
      }

You can easily auto download file with using Blob and transfer it in first param downloadJsonFile. filename is name of file you wanna set.

Nam Do
  • 83
  • 1
  • 7
6

If you prefer console snippet, raser, than filename, you can do this:

window.open(URL.createObjectURL(
    new Blob([JSON.stringify(JSON)], {
      type: 'application/binary'}
    )
))
zb'
  • 8,071
  • 4
  • 41
  • 68
5

The download property of links is new and not is supported in Internet Explorer (see the compatibility table here). For a cross-browser solution to this problem I would take a look at FileSaver.js

robertjd
  • 4,723
  • 1
  • 25
  • 29
5

React: add this where you want in your render method.

• Object in state:

<a
  className="pull-right btn btn-primary"
  style={{ margin: 10 }}
  href={`data:text/json;charset=utf-8,${encodeURIComponent(
  JSON.stringify(this.state.objectToDownload)
  )}`}
  download="data.json"
>
  DOWNLOAD DATA AS JSON
</a>

• Object in props:

<a
  className="pull-right btn btn-primary"
  style={{ margin: 10 }}
  href={`data:text/json;charset=utf-8,${encodeURIComponent(
  JSON.stringify(this.props.objectToDownload)
  )}`}
  download="data.json"
>
  DOWNLOAD DATA AS JSON
</a>

className and style are optional, modify the style according to your needs.

Abido
  • 752
  • 6
  • 18
2

Try to set another MIME-type: exportData = 'data:application/octet-stream;charset=utf-8,';

But there are can be problems with file name in save dialog.

minimulin
  • 41
  • 4
  • Sorry for coming back to you late. I tried your answer and it does download file but contains wrong data in it.. :( – Eugene Yu Nov 05 '13 at 22:14
  • 2
    this worked for me... `data = "data:application/octet-stream;charset=utf-8," + encodeURIComponent(JSON.stringify(data)); window.open(data);` it simply downloads the file as "download" but the data i stringified and then uri-encoded is as it should be. – Jason Wiener Oct 07 '14 at 15:47
2

Here's a simple version adapted from @Mohammed/@Maia working in modern browsers:

function saveAsFile(filename, data) {

    const blob = new Blob([JSON.stringify(data)]);
    const link = document.createElement("a");
    link.download = filename;
    link.href = window.URL.createObjectURL(blob);
    link.click()

};

Which can be used as

saveAsFile('posts.json', posts)
0

const exportToJson = (data: {}) =>{

const link = document.createElement("a");
link.href = data:text/json;charset=utf8,${encodeURIComponent(JSON.stringify(data))}; link.download = 'example.json'; link.click(); }

Make sure to clean up the the created link after if you don't want a random element that does nothing.