Repository: node-red/node-red (js)

Issue:
CSV  node omits value of "undefined" from output
### Current Behavior

If a value in a CSV field is the string `"undefined"` it will be not be included in the output.

### Expected Behavior

The string `"undefined"` should be treated like any other string and  included in the output.


### Steps To Reproduce

Run the flow below. 

It passes the following object to the `CSV` node configured with a template of `a,b,c,d,e` with a command separator:

```js
msg.payload = {
    a: undefined,
    b: "undefined",
    d: null,
    e: "defined"
```

The payload output is: `,,,null,defined`

Note that first three values fields are empty strings in the output: the primitive value `undefined`, the string `"undefined"`, and a missing property. 

The expected output is `,undefined,,null,defined`.

The problem appears to be on [lines 133 to 135](https://github.com/node-red/node-red/blob/f060309002e41ce0ab610b7d78f58f0ccdb4042d/packages/node_modules/%40node-red/nodes/core/parsers/70-CSV.js#L133-L135) of the CSV parser. I think the code should check if the value returned by `RED.util.getMessageProperty` is `undefined` before calling `RED.util.ensureString`, something like this:

```
var p = RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']");
p = (undefined === p) "" : RED.util.ensureString(p);
```


### Example flow

```
[
    {
        "id": "a7eca55ee1dc2da2",
        "type": "tab",
        "label": "Flow 3",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "4444396a1e39daed",
        "type": "inject",
        "z": "a7eca55ee1dc2da2",
        "name": "single",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "_mcu": false,
        "x": 230,
        "y": 220,
        "wires": [
            [
                "486a208de2734943"
            ]
        ]
    },
    {
        "id": "e9fbbb11c02df732",
        "type": "csv",
        "z": "a7eca55ee1dc2da2",
        "name": "",
        "sep": ",",
        "hdrin": "",
        "hdrout": "none",
        "multi": "one",
        "ret": "\\n",
        "temp": "a,b,c,d,e",
        "skip": "0",
        "strings": true,
        "include_empty_strings": false,
        "include_null_values": false,
        "_mcu": false,
        "x": 550,
        "y": 280,
        "wires": [
            [
                "a29b411423514a9b"
            ]
        ]
    },
    {
        "id": "a29b411423514a9b",
        "type": "debug",
        "z": "a7eca55ee1dc2da2",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": true,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "_mcu": false,
        "x": 710,
        "y": 220,
        "wires": []
    },
    {
        "id": "486a208de2734943",
        "type": "function",
        "z": "a7eca55ee1dc2da2",
        "name": "",
        "func": "msg.payload = {\n    a: undefined,\n    b: \"undefined\",\n    d: null,\n    e: \"defined\"\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "_mcu": false,
        "x": 380,
        "y": 220,
        "wires": [
            [
                "e9fbbb11c02df732",
                "a29b411423514a9b"
            ]
        ]
    }
]```


### Environment

- Node-RED version: v2.2.2
- Node.js version: v18.8.0
- npm version: 8.18.0
- Platform/OS: macOS
- Browser: Chrome

Pull request description:
Fix for CSV undefined property
to close #3900 main issue
and add tests
(other fix is commented out but no tests)

<!--
## Before you hit that Submit button....

Please read our [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
before submitting a pull-request.

## Types of changes

What types of changes does your code introduce?
Put an `x` in the boxes that apply
-->

- [X] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)

<!--
If you want to raise a pull-request with a new feature, or a refactoring
of existing code, it **may well get rejected** if it hasn't been discussed on
the [forum](https://discourse.nodered.org) or
[slack team](https://nodered.org/slack) first.

-->

## Proposed changes

As per #3900 the CSV node omits a string "undefined" if present when going from object to csv - as it assumes it is undefined :-)...
This fix - fixes that - and adds tests.
It also add some extra fixes to actually honour the "include nulls flag" that is currently only used when going from csv to object and not this way round (obj - csv). But these are commented out as they are a change in behaviour so we may not want to release them as-is without nore thought... (and would need changes to tests).

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [X] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [X] I have run `grunt` to verify the unit tests pass
- [X] I have added suitable unit tests to cover the new/changed functionality


Patch:
diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
index 9c55fa2b6..39d03f05c 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
@@ -110,7 +110,12 @@ module.exports = function(RED) {
                                         if (msg.payload[s].hasOwnProperty(p)) {
                                             /* istanbul ignore else */
                                             if (typeof msg.payload[s][p] !== "object") {
-                                                var q = "" + msg.payload[s][p];
+                                            // Fix to honour include null values flag
+                                            //if (typeof msg.payload[s][p] !== "object" || (node.include_null_values === true && msg.payload[s][p] === null)) {
+                                                var q = "";
+                                                if (msg.payload[s][p] !== undefined) {
+                                                    q += msg.payload[s][p];
+                                                }
                                                 if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
                                                     q = q.replace(/"/g, '""');
                                                     ou += node.quo + q + node.quo + node.sep;
@@ -130,9 +135,12 @@ module.exports = function(RED) {
                                             ou += node.sep;
                                         }
                                         else {
-                                            var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']"));
+                                            var p = RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']");
                                             /* istanbul ignore else */
-                                            if (p === "undefined") { p = ""; }
+                                            if (p === undefined) { p = ""; }
+                                            // fix to honour include null values flag
+                                            //if (p === null && node.include_null_values !== true) { p = "";}
+                                            p = RED.util.ensureString(p);
                                             if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
                                                 p = p.replace(/"/g, '""');
                                                 ou += node.quo + p + node.quo + node.sep;


Test patch:
diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index 93d59a171..d56f05db3 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -693,19 +693,20 @@ describe('CSV node', function() {
     describe('json object to csv', function() {
 
         it('should convert a simple object back to a csv', function(done) {
-            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f", wires:[["n2"]] },
+            var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f,g,h,i,j,k", wires:[["n2"]] },
                 {id:"n2", type:"helper"} ];
             helper.load(csvNode, flow, function() {
                 var n1 = helper.getNode("n1");
                 var n2 = helper.getNode("n2");
                 n2.on("input", function(msg) {
+                    // console.log("GOT",msg)
                     try {
-                        msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld"\n');
+                        msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld",,,undefined,null,null\n');
                         done();
                     }
                     catch(e) { done(e); }
                 });
-                var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld" };
+                var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld", h:undefined, i:"undefined",j:null,k:"null" };
                 n1.emit("input", {payload:testJson});
             });
         });
@@ -717,13 +718,14 @@ describe('CSV node', function() {
                 var n1 = helper.getNode("n1");
                 var n2 = helper.getNode("n2");
                 n2.on("input", function(msg) {
+                    // console.log("GOT",msg)
                     try {
-                        msg.should.have.property('payload', '1,foo,"ba""r","di,ng"\n');
+                        msg.should.have.property('payload', '1,foo,"ba""r","di,ng",,undefined,null\n');
                         done();
                     }
                     catch(e) { done(e); }
                 });
-                var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng" };
+                var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng", e:undefined, f:"undefined", g:null,h:"null" };
                 n1.emit("input", {payload:testJson});
             });
         });