How to Convert a javascript object to utf-8 Blob for download?
Asked Answered
F

3

13

I've been trying to find a solution that works but couldn't find one.

I have an object in javascript and it has some non-english characters in it.
I'm trying the following code to convert the object to a blob for download.
When I click to download the content, when opening the downloaded JSON the non-English characters are gibberish.

It's a simple object like this one: {name: "שלומית", last: "רעננה"}

function setJSONForDownload(obj) {
    obj = obj || []; // obj is the array of objects with non-english characters
    const length = obj.length;
    if (length) {
      const str = JSON.stringify(obj);
      const data = encode( str );

      const blob = new Blob( [ data ], {
        type: "application/json;charset=utf-8"
     });

      const url = URL.createObjectURL( blob );
      const downloadElem = document.getElementById('download');
      downloadElem.innerText = `Download ${length} pages scraped`;
      downloadElem.setAttribute( 'href', url );
      downloadElem.setAttribute( 'download', 'data.json' );
    }
    else {
      document.getElementById('download').innerText = `No data to download...`;
    }
}

function encode (s) {
  const out = [];
  for ( let i = 0; i < s.length; i++ ) {
    out[i] = s.charCodeAt(i);
  }
  return new Uint8Array(out);
}
Frisk answered 26/12, 2018 at 7:54 Comment(4)
Can you share the data you set in the blob? it's just a text with non-english characters or it's something else?Kill
Please look at the link-https://mcmap.net/q/836988/-file-not-downloading-with-blob-object-in-iphone-chrome-browserCrabbe
Return a Uint16Array(). Char codes are 16 bits, not 8. Then make the type "application/json;charset=utf-16" as well.Cavit
@Kill it's a simple object like this one: {name: "שלומית", last: "רעננה"}Frisk
D
39

Your encode function is broken, as it casts charcodes to bytes. Don't try to implement this yourself, just use the Encoding API:

const str = JSON.stringify(obj);
const bytes = new TextEncoder().encode(str);
const blob = new Blob([bytes], {
    type: "application/json;charset=utf-8"
});
Danicadanice answered 26/12, 2018 at 13:39 Comment(7)
According to your profile image and this simple code I'm sure you're a magician :) Works like a charm, thanks!Frisk
While it won't be a problem here, because it won't be used, note that even if not per se forbidden, setting the ;charset=nnn will make your MIMEType invalid to some browsers (Safari at least). Better not set it, it doesn't have any incidence whatsoever on the content anyway.Ragman
And I missed it earlier, but the conversion DOMString to UTF-8 is implicitly done by new Blob([DOMString])Ragman
@Ragman Thanks, I had guessed there was something even simpler but didn't take the time to look it up. Have an upvote!Danicadanice
thank you , sir . the code is so concise that i used it in my code immediately !Timtima
not sure why, but i see the hebrew characters everywhere - except Excel - what am i doing wrong ?Tantara
@RickyLevi Most likely an Excel problem then. It's known not to be good with encodings…Danicadanice
R
6

Calling new Blob([DOMString]) will automatically convert your DOMString (UTF-16) to UTF-8.

So all you need is new Blob( [JSON.stringify(obj)] ).

setJSONForDownload([{ name: "שלומית", last: "רעננה"}]);

function setJSONForDownload(obj) {
  obj = obj || [];
  const length = obj.length;
  if (length) {

    // DOMString
    const str = JSON.stringify(obj);
    // text/plain;UTF-8
    const blob = new Blob([str]);
    
    const url = URL.createObjectURL(blob);
    const downloadElem = document.getElementById('download');
    downloadElem.innerText = `Download ${length} pages scraped`;
    downloadElem.setAttribute('href', url);
    downloadElem.setAttribute('download', 'data.json');
  } else {
    document.getElementById('download').innerText = `No data to download...`;
  }
}
<a id="download">dl</a>
Ragman answered 27/12, 2018 at 9:51 Comment(0)
F
1

I found a nice block of code that solved my issue.
Thanks to 'pascaldekloe' (https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330).

Just changed the encode method to the following:

function encode(s) {
    var i = 0, bytes = new Uint8Array(s.length * 4);
    for (var ci = 0; ci != s.length; ci++) {
        var c = s.charCodeAt(ci);
        if (c < 128) {
            bytes[i++] = c;
            continue;
        }
        if (c < 2048) {
            bytes[i++] = c >> 6 | 192;
        } else {
            if (c > 0xd7ff && c < 0xdc00) {
                if (++ci >= s.length)
                    throw new Error('UTF-8 encode: incomplete surrogate pair');
                var c2 = s.charCodeAt(ci);
                if (c2 < 0xdc00 || c2 > 0xdfff)
                    throw new Error('UTF-8 encode: second surrogate character 0x' + c2.toString(16) + ' at index ' + ci + ' out of range');
                c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff);
                bytes[i++] = c >> 18 | 240;
                bytes[i++] = c >> 12 & 63 | 128;
            } else bytes[i++] = c >> 12 | 224;
            bytes[i++] = c >> 6 & 63 | 128;
        }
        bytes[i++] = c & 63 | 128;
    }
    return bytes.subarray(0, i);
}
Frisk answered 26/12, 2018 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.