61

Is there a way to import CSV or JSON to firebase cloud firestore like in firebase realtime database?

enter image description here

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
Ted
  • 22,696
  • 11
  • 95
  • 109

13 Answers13

70

General Solution

I've found many takes on a script allowing to upload a JSON but none of them allowed sub-collections. My script above handles any level of nesting and sub-collections. It also handles the case where a document has its own data and sub-collections. This is based on the assumption that collection is array/object of objects (including an empty object or array).

To run the script make sure you have npm and node installed. Then run your code as node <name of the file>. Note, there is no need to deploy it as a cloud funciton.

const admin = require('../functions/node_modules/firebase-admin');
const serviceAccount = require("./service-key.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://<your-database-name>.firebaseio.com"
});

const data = require("./fakedb.json");

/**
 * Data is a collection if
 *  - it has a odd depth
 *  - contains only objects or contains no objects.
 */
function isCollection(data, path, depth) {
  if (
    typeof data != 'object' ||
    data == null ||
    data.length === 0 ||
    isEmpty(data)
  ) {
    return false;
  }

  for (const key in data) {
    if (typeof data[key] != 'object' || data[key] == null) {
      // If there is at least one non-object item in the data then it cannot be collection.
      return false;
    }
  }

  return true;
}

// Checks if object is empty.
function isEmpty(obj) {
  for(const key in obj) {
    if(obj.hasOwnProperty(key)) {
      return false;
    }
  }
  return true;
}

async function upload(data, path) {
  return await admin.firestore()
    .doc(path.join('/'))
    .set(data)
    .then(() => console.log(`Document ${path.join('/')} uploaded.`))
    .catch(() => console.error(`Could not write document ${path.join('/')}.`));
}

/**
 *
 */
async function resolve(data, path = []) {
  if (path.length > 0 && path.length % 2 == 0) {
    // Document's length of path is always even, however, one of keys can actually be a collection.

    // Copy an object.
    const documentData = Object.assign({}, data);

    for (const key in data) {
      // Resolve each collection and remove it from document data.
      if (isCollection(data[key], [...path, key])) {
        // Remove a collection from the document data.
        delete documentData[key];
        // Resolve a colleciton.
        resolve(data[key], [...path, key]);
      }
    }

    // If document is empty then it means it only consisted of collections.
    if (!isEmpty(documentData)) {
      // Upload a document free of collections.
      await upload(documentData, path);
    }
  } else {
    // Collection's length of is always odd.
    for (const key in data) {
      // Resolve each collection.
      await resolve(data[key], [...path, key]);
    }
  }
}

resolve(data);
Mayur Dhurpate
  • 1,112
  • 4
  • 16
  • 34
Maciej Caputa
  • 1,831
  • 12
  • 20
  • 2
    This worked great for me, this is a much better (robust and general) solution than one of Mikki. Use this one – the setup is the same btw so you try either this one or the one from Mikki very easily – just replacing the actual code. – inteist Nov 30 '17 at 23:06
  • 1
    @Maciej Caputa, Your solution works great. Thanks. However can you suggest how would you modify the fakedb.json so that it generates auto Id, rather than sequence - 1, 2, 3, ... – Rinav Jan 05 '18 at 14:17
  • 1
    @Rinav You can have an array treated either as a collection with auto ID or a sequence 0,1,2,3... It all depends on the structure of your JSON file. It all comes down to the fact that array on a property with an even number of items in a path will be treated as sequence and if odd it will be treated as a collection. I have implemented this way but note that this is actually only compliant way with Firebase specification. I wonder when Firebase will implement this in firebase tools. – Maciej Caputa Jan 06 '18 at 14:39
  • @MaciejCaputa thanks, your comment cleared one of my doubt – Rinav Jan 09 '18 at 09:58
  • Would it be possible for anyone to please elaborate on how to use this script for beginners please... 1) I have copied it into my index.js file 2) downloaded the admin service key and saved it on my desktop, then replaced ""./service-key.json" in the service account constant with its file location 3) filled in the with mine 4) replaced "./fakedb.json" with the path to the json file that I would like uploaded. Am I missing anything? I noticed that this ->> '../functions/node_modules/firebase-admin' starts with two periods, is that correct or a typo? – pho_pho Mar 02 '18 at 00:09
  • 1
    You need to ensure that you have node and npm installed. Then you need to install firebase-admin and its dependencies. The path that you refer to has no typo in it, it is a reference to the package in the node module that is required by this solution. You should replace it with your own, however, this one assumes that in your folder structure you have a folder for uploading the data next to the functions one. – Maciej Caputa Mar 02 '18 at 11:27
  • I am receiving the error "14:20 error Parsing error: Unexpected token : ", which I suppose indicates that there is an error in your script such that the JSON is not being parsed to the firestorm data structure? – pho_pho Mar 02 '18 at 16:15
  • 1
    That most likely means that your JSON file is not correct, you can use online validators to make sure it is correctly formatted. – Maciej Caputa Mar 02 '18 at 16:18
  • Thanks for the script Maciej, I was trying to deploy it using the firebase CLI (`firebase deploy --only functions`). I found that instead you run it using `node index.js` and it works perfectly. – pho_pho Mar 03 '18 at 14:54
  • 2
    Happy to help.I added a note to my answer for further reference. – Maciej Caputa Mar 03 '18 at 19:40
  • 4
    It would be great if you could also provide an example of the JSON structure to use to feed the database. I ran the script and it created a separate collection for each block of data and each character in the fields came up as a separate field. Now I have a big mess to clean up and there is no bulk delete function... – pasx Jun 06 '18 at 10:20
  • I am getting this error >> Auth error:Error: invalid_grant: Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values and use a clock with skew to account for clock differences between systems. – Umer Jul 22 '18 at 08:01
  • seems that API has been changed: getting a bunch of 'UnhandledPromiseRejectionWarning'. Fixed by replacing 'firebase-admin' module with 'firebase' – divideByZero Jan 14 '19 at 02:22
  • How do I get subcollections working in this? I tried a json file with `"title": {"id":1}` but that only creates a map. – Carrein Jan 23 '19 at 11:54
  • 2
    Here is an example of subcollection: { "testCollection": { "testDocument": { "testNestedCollection": { "testNestedDocument": { "nestedName": "Jack" } }, "name": "John" } } }. Before using the script you should use a JSON linter such as https://jsonlint.com to ensure your syntax is correct. – michael-martinez Feb 24 '19 at 15:28
  • Here are additional advices for NodeJS beginners: `npm -v && node -v && npm init` (this will create the configuration file, default options should be fine). Then replace `../functions/` with `./` if your node_nodules/ folder is in your working directory. Then grab the admin keys from the https://console.firebase.google.com/u/0/project//settings/serviceaccounts/adminsdk webpage and you are good to go. – michael-martinez Feb 24 '19 at 15:40
21

You need a custom script to do that.

I wrote one based on the Firebase admin SDK, as long as firebase library does not allow you to import nested arrays of data.

const admin = require('./node_modules/firebase-admin');
const serviceAccount = require("./service-key.json");

const data = require("./data.json");

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://YOUR_DB.firebaseio.com"
});

data && Object.keys(data).forEach(key => {
    const nestedContent = data[key];

    if (typeof nestedContent === "object") {
        Object.keys(nestedContent).forEach(docTitle => {
            admin.firestore()
                .collection(key)
                .doc(docTitle)
                .set(nestedContent[docTitle])
                .then((res) => {
                    console.log("Document successfully written!");
                })
                .catch((error) => {
                    console.error("Error writing document: ", error);
                });
        });
    }
});

Update: I wrote an article on this topic - Filling Firestore with data

Kanso Code
  • 7,479
  • 5
  • 34
  • 49
10

There is not, you'll need to write your own script at this time.

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
  • do you have any links on how to write a script, thanks! – Ted Oct 09 '17 at 17:06
  • 4
    I love how Firebase goes to great lengths on stackoverflow to see what developers need, but it'd be great if these efforts eventually lead to features being added. I'd say JSON/CSV upload would tremendous :) – Dan Sabin Jan 13 '19 at 00:23
9

I used the General Solution provided by Maciej Caputa. Thank you (:

Here are a few hints. Assuming that you have an Ionic Firebase application installed with the required Firebase node modules in the functions folder inside that solution. This is a standard Ionic Firebase install. I created an import folder to hold the script and data at the same level.

Folder Hierarchy

myIonicApp
    functions
        node_modules
            firebase-admin
ImportFolder
    script.js
    FirebaseIonicTest-a1b2c3d4e5.json
    fileToImport.json

Script Parameters

const admin = require('../myIonicApp/functions/node_modules/firebase-admin'); //path to firebase-admin module
const serviceAccount = require("./FirebaseTest-xxxxxxxxxx.json"); //service account key file

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://fir-test-xxxxxx.firebaseio.com" //Your domain from the hosting tab
});

Domain name from the Hosting tab

Creating the Service Account Key File

  • In the Firebase console for your project, next to the Project Overwiew item, click on the gear icon and select Users and permissions
  • At the bottom of the screen, click Advanced permission settings

Accessing Google Cloud Platform Console

  • This opens another tab for the Google Cloud Platform Console
  • On the left select the Service Accounts item
  • Create a Service Account for an existing Service Account

I simply added a key to the App Engine default service account

The Create key function will offer to download the key to a JSON file

Creating the Service Account Key

JSON Data Structure

To use the script provided, the data structure must be as follows:

{
  "myCollection" : {
    "UniqueKey1" : {
      "field1" : "foo",
      "field2" : "bar"
    },{
    "UniqueKey2" : {
      "field1" : "fog",
      "field2" : "buzz"
    }...
}
pasx
  • 2,718
  • 1
  • 34
  • 26
  • This and Mikki's article helped me to get Maciej's script working. It would be nice for posterity if Maciej put it all together in his answer. There is one error in your example JSON here, although without it I was having issues with arrays behaving differently than I expected. The error is in the line that looks like },{. that extra { needs to be gone unless we are trying to make a subcollection. – KhanKhuu Mar 20 '20 at 20:03
8

For reference. I wrote a function that helps to import and export data in Firestore.

https://github.com/dalenguyen/firestore-import-export (archived)

Updated 2023:

Use this package instead. Thanks, @Webber

https://github.com/dalenguyen/firestore-backup-restore

Dale Nguyen
  • 1,930
  • 3
  • 24
  • 37
2

https://gist.github.com/JoeRoddy/1c706b77ca676bfc0983880f6e9aa8c8

This should work for an object of objects (generally how old firebase json is set up). You can add that code to an app that's already configured with Firestore.

Just make sure you have it pointing to the correct JSON file.

Good luck!

Joe Roddy
  • 729
  • 6
  • 12
2

This workaround in Python may help some people. First convert json or csv to dataframe using Pandas, then convert dataframe to dictionary and upload dictionary to firestore.

import firebase_admin as fb
from firebase_admin import firestore
import pandas as pd

cred = fb.credentials.Certificate('my_credentials_certificate.json')
default_app = fb.initialize_app(cred)
db = firestore.client()

df = pd.read_csv('data.csv')
dict = df.to_dict(orient='records')

my_doc_ref = db.collection('my_collection').document('my_document')
my_doc_ref.set(dict)

There could be similar workarounds in javascript and other languages using libraries similar to Pandas.

Mutlu Simsek
  • 1,088
  • 14
  • 22
1

No as of now, you can't.. firestore structures data into a different format that is, using collections and each collection has a series of documents which then are stored in JSON format.. in future they might make a tool to convert JSON in to firestore.. for reference check this out

:https://cloud.google.com/firestore/docs/concepts/structure-data

****EDIT :****

You can automate the process up to some extent, that is, write a mock software which only pushes the fields of your CSV or JSON data into your Cloud Firestore DB. I migrated my whole database my making just a simple app which retrieved my DB and pushed it on Firestore

Anubhav Malik
  • 242
  • 7
  • 13
1
var admin = require('firebase-admin');

var serviceAccount = require('./serviceAccountKey.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://csvread-d149c.firebaseio.com'
});

const csv = require('csv-parser');  
const fs = require('fs');

const firestore = admin.firestore();

// CSV FILE data.csv

// Name,Surname,Age,Gender
// John,Snow,26,M  
// Clair,White,33,F  
// Fancy,Brown,78,F

fs.createReadStream('data.csv')  
  .pipe(csv())
  .on('data', (row) => {
    console.log(row);
    if(row) {
      firestore.collection('csv').add({
        name: row.Name,
        surname: row.Surname,
        age: row.Age,
        sex: row.Gender
      });
    }
    else {
      console.log('No data')
    }
  })
  .on('end', () => {
    console.log('CSV file successfully processed');
  });
1

There is now a paid GUI tool that does this, Firefoo. I haven't used it, but it seems quite powerful for managing Firestore - including importing csv files:

enter image description here

David Stack
  • 129
  • 5
0

This is a small modification that copies the 'id' of the document, if exists, to its path. Otherwise it will use the "for"'s index.

  ...
  ...
  } else {
    // Collection's length of is always odd.
    for (const key in data) {
      // Resolve each collection.
      if (data[key]['id']!==undefined)
        await resolve(data[key], [...path,data[key]['id']])
      else 
        await resolve(data[key], [...path, key]);
    }
  }
}

resolve(data);
rgrcnh
  • 3
  • 2
0

1 - Importing only collections

If the names of your collections are not only composed of numbers, then you can define the name of the document as follows.


    ...
    ...

    } else {
        // Collection's length of is always odd.
        for (const key in data) {
          // // Resolve each collection.

          // If key is a number it means that it is not a collection
          // The variable id in this case will be the name of the document field or you 
          // can generate randomly
          let id = !isNaN(key) ? data[key].id : key;

          await resolve(data[key], [...path, id]);
        }
      }
    }

2 - Import collections and sub-collections

In the same way as in the example above, the name of the sub-collection can not contain only numbers.

    ...
    ...

    for (const key in data) {
      // Resolve each collection and remove it from document data.
      if (isCollection(data[key], [...path, key])) {
        // Remove a collection from the document data.
        delete documentData[key];

        // If key is a number it means that it is not a collection
        // The variable id in this case will be the name of the document field or you 
        // can generate randomly
        let id = !isNaN(key) ? data[key].id : key;

        // Resolve a colleciton.
        resolve(data[key], [...path, id]);
      }
    }

    ...
    ...

Note.: Changes in @Maciej Caputa's code

0

this library you can use https://www.npmjs.com/package/csv-to-firestore

install this library with this command npm i -g csv-to-firestore

put this script inside your ./scripts

    module.exports = {
      path: "./scripts/palabrasOutput.csv",
      firebase: {
        credential: "./scripts/serviceAccount.json",
        collection: "collectionInFirestore",
      },
      mapper: (dataFromCSV) => {
        return dataFromCSV;
      },
    };

and run:

csv-to-firestore --config ./scripts/csvToFirestore.js

btw: Generate new private key (it's a file in Service account section in firestore) put that file in /scripts folder and rename serviceAccount.json

rnewed_user
  • 1,386
  • 7
  • 13