Extracting zipped files using JSZIP in javascript
Asked Answered
U

4

21

In my webpage, a user is supposed to upload a zipped file. Within the zipped file are 2 files: another zip file and a txt file. On my server, after receiving the zip, I want to unzip the zip file to extract the zip & txt file, then move those 2 files to a predefined folder. I have a piece of code that extracts the zip file, but the data doesn't seem correct. Firstly, it unzipped a zip and 2 txt file when there should only be 1 txt file. It created an additional 'undefined' txt file. Also, in my txt file, instead of the original data, it was replaced with the following text: '[undefined] [undefined]'.
Can anyone help me on this? The following is my code:

var JSZip = require('JSZip');

fs.readFile( filePath, function(err, data){
  if (!err){
    var zip = new JSZip();
    JSZip.loadAsync(data).then(function(zip){
      object.keys(zip.files).forEach(function(filename){
        var content = zip.files[filename];
        var dest = path + filename;
        fs.writeFileSync(dest, content);
      });
    });
  }
});
Unorganized answered 5/9, 2016 at 1:44 Comment(1)
Is this code running in node? If so, you might want to add the tag for clarity.Jabon
T
23

It took a bit of digging in their documentation but they have an example that shows how to read the file contents from a ZIP.

You are getting the object that describes the ZIP contents but not the actual content. Here is an adjusted version:

var JSZip = require('JSZip');

fs.readFile(filePath, function(err, data) {
    if (!err) {
        var zip = new JSZip();
        zip.loadAsync(data).then(function(contents) {
            Object.keys(contents.files).forEach(function(filename) {
                zip.file(filename).async('nodebuffer').then(function(content) {
                    var dest = path + filename;
                    fs.writeFileSync(dest, content);
                });
            });
        });
    }
});
Telstar answered 5/9, 2016 at 5:38 Comment(11)
Hi, thank you for the response. I tried it out but its not even unzipping now. Have u tried it out? Is it working for u?Unorganized
I think something went wrong with this line "zip.files(filename).async('nodebuffer').then(function(content) {". I did a print and the print doesnt show after this line.Unorganized
.async('nodebuffer') seems to the the problem. I did a print using .async and it will get stuck there.Unorganized
I have tried it and it works for me. Are you running this in node.js (if yes, what version) or in the browser?Telstar
To show errors (if any), add a catch callback on the promise: .then(function(content) {...}).catch(function(e) {console.error(e)})Stephan
Yes i am using node.js. My code is in my controller, which shouldnt be a problem either right? For some reason whenever i added .async it wont work..Unorganized
Im using sails v0.12.3, and after adding the catch, it didnt cstch anything.Unorganized
Here's the running sample plus the test.zip: code.runnable.com/V85uLPcBDC5wwPy7/…Telstar
Oh dear it worked.. but instead of JSZip.loadAsync, i used zip.loadAsyncUnorganized
@QLiu - It's generally correct but there are a few issues with the code: object should be Object, zip.files(filename) should be zip.files[filename]. See my answer below which might help.Oquassa
files must be an ArrayBuffer, which can also come from the browser; i.e. a web requests can return an ArrayBuffer (see d3.buffer in the d3-fetch module); and a browser file upload can parse a user's local file to an ArrayBuffer with the File API)Rew
O
40

This is a working version I am using:

var jsZip = require('jszip')
jsZip.loadAsync(file).then(function (zip) {
  Object.keys(zip.files).forEach(function (filename) {
    zip.files[filename].async('string').then(function (fileData) {
      console.log(fileData) // These are your file contents      
    })
  })
})

You can get most of the information you need from http://stuk.github.io/jszip/documentation/examples.html but it's a little hard to get in one place, you have to look around a bit.

Oquassa answered 10/10, 2016 at 18:55 Comment(7)
Strange to get a downvote after so long - is there something that needs changing?Oquassa
@Oquassa I don't know why you got the downvote, but .async('string') doesn't work for me with the latest version and I would prefer arrow functions here.Sim
@YannicHamann you simply do .async('text').then((fileContents) => { // do stuffs with file contents });Annemarie
What does the variable file contain here exactly?Fetation
@Fetation It's a file from a HTML file selector i.e e.target.files[0] when listening for a change event on the form element.Oquassa
On the 2nd line, is file the zip file? Please be clear, because in the next answer he reads the zip file content first with fs.readFile()Callista
@JoãoPimentelFerreira See my comment above yours. It's a user selected file from the HTML file selector. Essentially, it'll just be the binary data of the file.Oquassa
T
23

It took a bit of digging in their documentation but they have an example that shows how to read the file contents from a ZIP.

You are getting the object that describes the ZIP contents but not the actual content. Here is an adjusted version:

var JSZip = require('JSZip');

fs.readFile(filePath, function(err, data) {
    if (!err) {
        var zip = new JSZip();
        zip.loadAsync(data).then(function(contents) {
            Object.keys(contents.files).forEach(function(filename) {
                zip.file(filename).async('nodebuffer').then(function(content) {
                    var dest = path + filename;
                    fs.writeFileSync(dest, content);
                });
            });
        });
    }
});
Telstar answered 5/9, 2016 at 5:38 Comment(11)
Hi, thank you for the response. I tried it out but its not even unzipping now. Have u tried it out? Is it working for u?Unorganized
I think something went wrong with this line "zip.files(filename).async('nodebuffer').then(function(content) {". I did a print and the print doesnt show after this line.Unorganized
.async('nodebuffer') seems to the the problem. I did a print using .async and it will get stuck there.Unorganized
I have tried it and it works for me. Are you running this in node.js (if yes, what version) or in the browser?Telstar
To show errors (if any), add a catch callback on the promise: .then(function(content) {...}).catch(function(e) {console.error(e)})Stephan
Yes i am using node.js. My code is in my controller, which shouldnt be a problem either right? For some reason whenever i added .async it wont work..Unorganized
Im using sails v0.12.3, and after adding the catch, it didnt cstch anything.Unorganized
Here's the running sample plus the test.zip: code.runnable.com/V85uLPcBDC5wwPy7/…Telstar
Oh dear it worked.. but instead of JSZip.loadAsync, i used zip.loadAsyncUnorganized
@QLiu - It's generally correct but there are a few issues with the code: object should be Object, zip.files(filename) should be zip.files[filename]. See my answer below which might help.Oquassa
files must be an ArrayBuffer, which can also come from the browser; i.e. a web requests can return an ArrayBuffer (see d3.buffer in the d3-fetch module); and a browser file upload can parse a user's local file to an ArrayBuffer with the File API)Rew
F
1

Here was my strategy in Angular 10 to write a single file to a zip with a custom extension, then later read that same zip to retrieve the json.

Package the file into a zip (custom file endings supported)

import { saveAs } from 'file-saver';
import * as JSZip from 'jszip';

export async function exportJson(
  filename: string,
  jsonToExport: string,
  fileNameEnding = '.zip'
): Promise<string> {
  const jsonFile = new Blob([jsonToExport], {
    type: 'application/json',
  });

  if (!jsonFile) return Promise.reject('Error converting file to JSON');

  const zipper = new JSZip();
  zipper.file(`${filename}.json`, jsonFile);
  const zippedFile = await zipper.generateAsync({ type: 'blob' });

  const exportFilename = `${filename}${fileNameEnding}`;
  saveAs(zippedFile, exportFilename);
  return Promise.resolve(exportFilename);
}

Read the file contents from the zip

// file parameter retrieved from an input type=file
export async function readExportedJson(file: File): Promise<Blob> {
  const zipper = new JSZip();
  const unzippedFiles = await zipper.loadAsync(file);
  return Promise.resolve(unzippedFiles).then(unzipped => {
    if (!Object.keys(unzipped.files).length) {
      return Promise.reject('No file was found');
    }
    return unzipped.files[Object.keys(unzipped.files)[0]];
  }).then(unzippedFile => zipper.file(unzippedFile.name).async('string'));
}
Fugitive answered 11/11, 2021 at 21:0 Comment(0)
S
0

This answer is cordova-plugin-file specific.

As stated in the docs:

Directory entries have to be created successively. For example, the call fs.root.getDirectory('dir1/dir2', {create:true}, successCallback, errorCallback) will fail if dir1 did not exist.

I am almost certain that the currently accepted answer cannot guarantee that file content/folders are always retrieved in the same order. This could result in problems with an API such as cordova-plugin-file. Especially when you invoke another async function to asynchronously create the directory on the filesystem.

You may want to filter the directories of your zip archive first and create them in a sync manner before continuing to extract other files as already answered:

const directoryNames = Object.keys(zip.files).filter(name => zip.files[name].dir);
for (const directoryName of directoryNames) {
    await this.createDirectory(directoryName, dirEntry);
}
// ...

private createDirectory = (dirName: string, dirEntry: DirectoryEntry) => {
    const promise = new Promise<DirectoryEntry>(resolve, reject) => {
        dirEntry.getDirectory(dirName, { create: true }, dirEntry => {
            resolve(dirEntry);
        }, fileError => reject(fileError));
    });
    return promise;
}
Sim answered 23/7, 2019 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.