🗜️ Zip a file in Go

compression file zip

To zip a file or a directory in Go using the standard library, use zip.Writer type from the archive/zip package. Creating a compressed archive using this method involves going through all the files you want to include, generating a local file header for each one, and writing its contents to the resulting ZIP file.

Check also our example on how to unzip a file in Go

package main

import (
    "archive/zip"
    "io"
    "log"
    "os"
    "path/filepath"
)

func zipSource(source, target string) error {
    // 1. Create a ZIP file and zip.Writer
    f, err := os.Create(target)
    if err != nil {
        return err
    }
    defer f.Close()

    writer := zip.NewWriter(f)
    defer writer.Close()

    // 2. Go through all the files of the source
    return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        // 3. Create a local file header
        header, err := zip.FileInfoHeader(info)
        if err != nil {
            return err
        }

        // set compression
        header.Method = zip.Deflate

        // 4. Set relative path of a file as the header name
        header.Name, err = filepath.Rel(filepath.Dir(source), path)
        if err != nil {
            return err
        }
        if info.IsDir() {
            header.Name += "/"
        }

        // 5. Create writer for the file header and save content of the file
        headerWriter, err := writer.CreateHeader(header)
        if err != nil {
            return err
        }

        if info.IsDir() {
            return nil
        }

        f, err := os.Open(path)
        if err != nil {
            return err
        }
        defer f.Close()

        _, err = io.Copy(headerWriter, f)
        return err
    })
}

func main() {
    if err := zipSource("testFolder", "testFolder.zip"); err != nil {
        log.Fatal(err)
    }
    if err := zipSource("testFolder/1/1.1/1.1.txt", "1.1.zip"); err != nil {
        log.Fatal(err)
    }
}

In the example above, we use a sample directory testFolder:

testFolder
├── 1
│   └── 1.1
│       └── 1.1.txt
├── 2
└── test.txt


The zipSource(source, target string) function does the following:

  1. Create a ZIP file and zip.Writer
    // 1. Create a ZIP file and zip.Writer
    f, err := os.Create(target)
    if err != nil {
        return err
    }
    defer f.Close()
    
    writer := zip.NewWriter(f)
    defer writer.Close()
    

    In the first step, we need to create a ZIP file and initialize zip.Writer that writes the compressed data to the file. Notice that we defer closing the file and the writer to the end of the zipSource() function.

  2. Go through all the files of the source
    // 2. Go through all the files of the source
    return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
    ...
    })
    

    We want the zipSource() function to work on both a single file and a folder, so we need to walk through each file of the source path using the filepath.Walk() function.

  3. Create a local file header
    // 3. Create a local file header
    header, err := zip.FileInfoHeader(info)
    if err != nil {
        return err
    }
    
    // set compression
    header.Method = zip.Deflate
    

    For each file in the archive, we need to create a local file header using the FileInfoHeader() function. The generated header does not have the compression method set, so we explicitly set it to zip.Deflate.

  4. Set the relative path of a file as the header name
    // 4. Set relative path of a file as the header name
    header.Name, err = filepath.Rel(filepath.Dir(source), path)
    if err != nil {
        return err
    }
    if info.IsDir() {
        header.Name += "/"
    }
    

    In the previous step, we created a file header using FileInfoHeader(fi fs.FileInfo) function with the name from fs.FileInfo argument. However, this is just a base name, so we need to modify it to preserve the directory structure in the generated ZIP. We do this by calculating the relative path from the source path directory to the specified file path using filepath.Dir() and filepath.Rel() functions. For example, compressing the /a/b/c directory with /a/b/c/d/test.txt file gives us:

    filepath.Rel(filepath.Dir("/a/b/c"), "/a/b/c/d/test.txt")
    

    which is equivalent to:

    filepath.Rel("/a/b/", "/a/b/c/d/test.txt")
    

    As a result, we get the relative path:

    "c/d/test.txt"
    

    Also, if the file is a directory, it should be marked with a trailing slash.

  5. Create writer for the file header and save content of the file
    // 5. Create a writer for the file header and save the content of the file
    headerWriter, err := writer.CreateHeader(header)
    if err != nil {
        return err
    }
    
    if info.IsDir() {
        return nil
    }
    
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()
    
    _, err = io.Copy(headerWriter, f)
    return err
    

    The last step is to create a writer for the specified file data based on the header. The data is transferred to the ZIP file using the io.Copy() function. Files that are directories should not be copied to avoid an error.

🗄️ Unzip a file in Go

Learn how to uncompress a zip file
compression file zip

📁 Create a directory in Go

Learn how to create a single or a hierarchy of directories
introduction file

📎 Convert JSON to CSV in Go

Learn how to transform JSON file to CSV
introduction file json csv