node-csvtojson icon indicating copy to clipboard operation
node-csvtojson copied to clipboard

downstreamFormat - Last in array to contain no comma?

Open CyrisXD opened this issue 6 years ago • 41 comments

Hi there, trying to read a CSV file and stream its output data to another JSON array file.

          var Converter = require("csvtojson").Converter;

          var csvConverter = new Converter({
            constructResult: false,
            downstreamFormat: "array"
          });

          var readStream = require("fs").createReadStream(req.file.path);

          var writeStream = require("fs").createWriteStream("csvData.json");

          readStream
            .pipe(csvConverter)
            .subscribe((jsonObj, index) => {
              jsonObj.myNewKey = "some value";
            })
            .pipe(writeStream);

But the last JSON added to the array adds an extra comma at the end creating an invalid array/json.

As such:

[
{Name: Bob},
{Name: Sarah},
{Name: James},
]

How could I make sure it doesn't add the comma on the last entry? Cheers

CyrisXD avatar Aug 13 '19 22:08 CyrisXD

The same for me ^^^

ushakov-ruslan avatar Aug 27 '19 12:08 ushakov-ruslan

@Keyang The same for me too ^^^

rami-res-zz avatar Aug 27 '19 12:08 rami-res-zz

Maybe if someone needs a quick solution until it's fixed:

import { Transform } from 'stream';

const csv = csvtojson(csvOptions);

// you can use one more stream to transform csv stream output
const transform = new Transform({
    transform (chunk, encoding, callback) {
      let string = chunk.toString('utf-8');

      if (['[\n', ']\n'].includes(string)) {
        if (string === '[\n') {
          this.theFirstEntity = true;
        }
        return callback(null, string);
      }

      string = string.replace(/,$/gm, '');

      if (this.theFirstEntity) {
        this.theFirstEntity = false;
      } else {
        string = `,` + string;
      }
      callback(null, string);
    },

  });

  readableStream.pipe(csv).pipe(transform).pipe(fs.createWriteStream('output.json'));
/*
output should be the following:
[
{"Name": "Bob"}
,{"Name": "Sarah"}
,{"Name": "James"}
]
*/

ushakov-ruslan avatar Aug 28 '19 05:08 ushakov-ruslan

@Keyang any updates on this?

ushakov-ruslan avatar Nov 19 '19 16:11 ushakov-ruslan

I had more luck with this lineToArray transform:

const { Transform } = require('stream');
const csvtojson = require("csvtojson");

const lineToArray = new Transform({
  transform (chunk, encoding, cb) {
    // add [ to very front
    // add , between rows
    // remove crlf from row
    this.push((this.isNotAtFirstRow ? ',' : '[') + chunk.toString('utf-8').slice(0, -1));
    this.isNotAtFirstRow = true;
    cb();
  },
  flush(cb) {
    // add ] to very end or [] if no rows
    const isEmpty = (!this.isNotAtFirstRow);
    this.push(isEmpty ? '[]' : ']');
    cb();
  }
});

readableStream
  .pipe(csvtojson({
    checkType: true,
    downstreamFormat: 'line'
  }))
  .pipe(lineToArray)
  .pipe(writableStream);

Also #389

Update: Modified to reflect comments on -1 https://github.com/Keyang/node-csvtojson/issues/333#issuecomment-705076249

oliverfoster avatar Dec 03 '19 10:12 oliverfoster

Is someone still maintaining this? I'm having the same exact issue.

mannyvergel avatar Feb 24 '20 02:02 mannyvergel

same issue

yuu2lee4 avatar Apr 14 '20 07:04 yuu2lee4

any update on this?

ravibadoni avatar May 19 '20 13:05 ravibadoni

@Keyang

ravibadoni avatar May 19 '20 13:05 ravibadoni

Maybe if someone needs a quick solution until it's fixed:

import { Transform } from 'stream';

const csv = csvtojson(csvOptions);

// you can use one more stream to transform csv stream output
const transform = new Transform({
    transform (chunk, encoding, callback) {
      let string = chunk.toString('utf-8');

      if (['[\n', ']\n'].includes(string)) {
        if (string === '[\n') {
          this.theFirstEntity = true;
        }
        return callback(null, string);
      }

      string = string.replace(/,$/gm, '');

      if (this.theFirstEntity) {
        this.theFirstEntity = false;
      } else {
        string = `,` + string;
      }
      callback(null, string);
    },

  });

  readableStream.pipe(csv).pipe(transform).pipe(fs.createWriteStream('output.json'));
/*
output should be the following:
[
{"Name": "Bob"}
,{"Name": "Sarah"}
,{"Name": "James"}
]
*/

hi @ushakov-ruslan Can you please give me a working example as this is not working for me. I need help in fixing comma. @ushakov-ruslan thanks

ravibadoni avatar May 20 '20 17:05 ravibadoni

did you try my example?

oliverfoster avatar May 20 '20 18:05 oliverfoster

yes @oliverfoster Please guide If I am doing something wrong.

`parse(){ // you can use one more stream to transform csv stream output const transform = new Transform({ transform (chunk, encoding, callback) { let string = chunk.toString('utf-8');

            if (['[\n', ']\n'].includes(string)) {
                if (string === '[\n') {
                    this.theFirstEntity = true;
                }
                return callback(null, string);
            }

            string = string.replace(/,$/gm, '');

            if (this.theFirstEntity) {
                this.theFirstEntity = false;
            } else {
                string = `,` + string;
            }
            callback(null, string);
        },

    });
    const self = this;
    return new Promise(function(resolve,reject){
        const filePath = require('path').resolve(
            __dirname+"/../../public/",
            self.fileName);
        const readStream= fs.createReadStream(filePath);
        var stream = fs.createWriteStream(filePath+".json");
        readStream.pipe(csv({downstreamFormat: 'array',flatKeys:true,delimiter:"auto",quote:'"',escape:'"',noheader: !self.isHeader,}))
            .on('header',(header)=>{
                header.map((header)=> header.toString().trim().replace(/[.]/g, ""))
                resolve(header);
            }).pipe(transform).pipe(stream)
    })
}`

ravibadoni avatar May 21 '20 05:05 ravibadoni

You're not using my example. I also couldn't get @ushakov-ruslan s example to work either which is why I left my example. Perhaps the code changed in between?

Mine explicitly switches to the line stream so that each write to the stream is a single JSON item or csv row. It makes everything much simpler to handle.

olivermartinfoster avatar May 21 '20 06:05 olivermartinfoster

        const lineToArray = new Transform({
            transform (chunk, encoding, cb) {
                this.push((this.isNotAtFirstRow ? ',' : '[') + chunk.toString('utf-8').slice(0,-2));
                this.isNotAtFirstRow = true;
                cb();
            },
            flush(cb) {
                const isEmpty = (!this.isNotAtFirstRow);
                this.push(isEmpty ? '[]' : ']');
                cb();
            }
        });
        const self = this;
        return new Promise(function(resolve,reject){
            const filePath = require('path').resolve(
                __dirname+"/../../public/",
                self.fileName);
            const readStream= fs.createReadStream(filePath);
            var stream = fs.createWriteStream(filePath+".json");
            readStream.pipe(csv({downstreamFormat: 'array',flatKeys:true,delimiter:"auto",quote:'"',escape:'"',noheader: !self.isHeader,}))
                .on('header',(header)=>{
                    header.map((header)=> header.toString().trim().replace(/[.]/g, ""))
                    resolve(header);
                }).pipe(lineToArray).pipe(stream)
        })
    }```
I used this. No array object json created inside the file

ravibadoni avatar May 21 '20 06:05 ravibadoni

will this work for downstreamFormat: 'array'

ravibadoni avatar May 21 '20 06:05 ravibadoni

Downstreamformat: 'line'. My transform is called lineToArray

olivermartinfoster avatar May 21 '20 06:05 olivermartinfoster

downstreamFormat: 'line' No data created in the file.

Also. I want to have array of object.

ravibadoni avatar May 21 '20 06:05 ravibadoni

readStream
  .pipe(csv({ 
    downstreamFormat: 'line', 
    checkType: true
  })
  .pipe(lineToArray)
  .pipe(stream);

Are you debugging? It looks like you're returning a promise from your parse function before creating the stream? It's hard to tell because there's no indentation. https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code

olivermartinfoster avatar May 21 '20 07:05 olivermartinfoster

image

ravibadoni avatar May 21 '20 07:05 ravibadoni

You're also not calling resolve on your promise.

olivermartinfoster avatar May 21 '20 07:05 olivermartinfoster

I am calling resolve on header to get all header values. The code is working with array of object, The only thing I am getting is an comma at the end

ravibadoni avatar May 21 '20 07:05 ravibadoni

Can you remove all the header stuff and just test it as I wrote it three messages ago? The line parser is not the array parser. Get the array of objects first then work out how to change the headers.

olivermartinfoster avatar May 21 '20 07:05 olivermartinfoster

        const lineToArray = new Transform({
            transform (chunk, encoding, cb) {
                this.push((this.isNotAtFirstRow ? ',' : '[') + chunk.toString('utf-8').slice(0,-2));
                this.isNotAtFirstRow = true;
                cb();
            },
            flush(cb) {
                const isEmpty = (!this.isNotAtFirstRow);
                this.push(isEmpty ? '[]' : ']');
                cb();
            }
        });
        const self = this;
            const filePath = require('path').resolve(
                __dirname+"/../../public/",
                self.fileName);
            const readStream= fs.createReadStream(filePath);
            var stream = fs.createWriteStream(filePath+".json");
        readStream
            .pipe(csv({
                downstreamFormat: 'line',
                checkType: true
            })).pipe(lineToArray).pipe(stream);
    }```

I get blank file without any data

ravibadoni avatar May 21 '20 07:05 ravibadoni

Your input filepath isn't right, the CSV isn't formatted correctly, you're not importing the library properly, you're dropping errors in a try catch block around your call to parse or something along those lines. Are you using a debugger? If you're getting an empty file the last line I know is running is the fs.createWriteStream.

olivermartinfoster avatar May 21 '20 07:05 olivermartinfoster

image

Input filepath is correct. CSV is formatted as It is wokring perfect with array of object only getting extra comma at the end. Please check screenshot for readstream data. It is not empty

ravibadoni avatar May 21 '20 07:05 ravibadoni

readStream.pipe(stream);

Does this make a copy of the file?

olivermartinfoster avatar May 21 '20 07:05 olivermartinfoster

yes correct. I am using streams.

            .pipe(csv({
                downstreamFormat: 'line',
                checkType: true
            })).pipe(lineToArray).pipe(stream);```

ravibadoni avatar May 21 '20 07:05 ravibadoni

What version of node are you using? I want to test myself. It definitely looks like an issue with the way I'm using streams.

olivermartinfoster avatar May 21 '20 07:05 olivermartinfoster

Node Version : v12.14.0

ravibadoni avatar May 21 '20 07:05 ravibadoni

csvtojson.zip

Works for me.

oliverfoster avatar May 21 '20 08:05 oliverfoster