Array, I just do not get it right

#1

Dear all,

it looks like I have a misunderstanding with arrays, where I just not come across.

I have the following code in a function node:

var localStations = flow.get("dataStations");
localStations = ( typeof localStations != "undefined" && localStations instanceof Array ) ? localStations : [];

function station(station, diesel, e5, e10) {
    return {
        station: station,
        diesel: diesel,
        e5: e5,
        e10: e10
    };
}

localStations.push({ key: msg.parts.key, station: station(msg.parts.key, msg.payload.diesel, msg.payload.e5, msg.payload.e10) });

flow.set("dataStations", localStations);

return localStations;

This produces an array which holds an array for each object – parts of the debug output:

But I expected something like an array which holds the different objects like this (exemplary):

array[3]
[0..3]
    0: object
    key: "Station 3"
    station: object
        station: "Station 3"
        diesel: 1.539
        e5: 1.639
        e10: false
    1: object
    key: "Station 1"
    station: object
        station: "Station 3"
        diesel: 1.539
        e5: 1.639
        e10: 1.569
...

Is that possible or I just do not see the mistake I made?

Thanks and Best
Aviation

#2

Are you sure you are not seeing a hangover from a previous run? Change the flow.get at the start to set localstations to an empty array and just send the node one message and see what happens then.

#3

You're creating an array in an array. For each input of data, you are making an empty array, pushing an object in it and then pushing the array you've made to the flow dataStations which appears to be an array (I'm not sure it doesn't say in the documentation). At least that's how I understand it, try just pushing the object to dataStations.

#4

@RoganFPS can you explain where he is making a new array for each time through?
Also I can only see one push.

#5

@Colin thanks for your feedback.
Tried it with a different variable, but got the same output:

array[5]
0: array[1]
    0: object
        key: "Station 3"
        station: object
            station: "Station 3"
            diesel: 1.559
            e5: 1.689
            e10: false
    _msgid: "9390d8fe.b78da8"
1: array[1]
2: array[1]
3: array[1]
4: array[1]

In addition I checked the message itself resp. the variable gets pushed to the array (localStations) this looks ok:

msg : Object
object
    key: "Station 3"
    station: object
        station: "Station 3"
        diesel: 1.559
        e5: 1.689
        e10: false
_msgid: "e096f445.bfa0b8"

And the input message going into the function node is looking like this:

msg : Object
object
payload: object
    e5: 1.689
    e10: false
    status: "open"
    diesel: 1.559
parts: object
    id: "7947f5b0.e672fc"
    type: "object"
    key: "Station 3"
    index: 0
    count: 5
_msgid: "e096f445.bfa0b8"

@RoganFPS thanks for your input but actually I cannot follow your ratio.

Every call of the node I check if the array within the flow exists if so I use it, if not I create an empty one:


var localStations = flow.get("dataStations");
localStations = ( typeof localStations != "undefined" && localStations instanceof Array ) ? localStations : [];

I just pushed the object itself to the array (without the key) but unfortunately I got the same results.

Push:


localStations.push(station(msg.parts.key, msg.payload.diesel, msg.payload.e5, msg.payload.e10));

Result:

array[5]
0: array[1]
    0: object
        station: "Station 3"
        diesel: 1.559
        e5: 1.689
        e10: false
    _msgid: "4d9df73f.afe6c8"
1: array[1]
2: array[1]
3: array[1]
4: array[1]

Someone may have a further idea what is wrong?

#6

The issue is you are returning localStations at the end of the function. The runtime receives that and treats it as an array of messages to send on. In part of that processing, it does some manipulations to the array - including ensuring each element of the array is an array, and ensuring each message object has a _msgid property.

As JavaScript does pass-by-reference, when the array is modified by the runtime, it is also modifying the same object you out into persistence - which is why you see the changes.

So, do you really mean to return that array to be treated as messages to be sent by the node?

If you do, you'll first need to clone it, to break the cross-reference.

return RED.util.cloneMessage(localStations)

1 Like
#7

Hi @Colin @Aviation is telling the function that localStation should = flow.dataStation and make sure its an array, then he/she goes on to fill localStation with some data which happens to be an object and pushes that to localStation array. Aviation then uses flow.set (At this point, I'm making a assumption/guess that flow.set is actually sugar syntax for whatever type its dealing with. E.g. array would be another array.push, objects would overwrite themselves) to add the data to the flow context. Effectively it's pushing an array to an array. We want to mutate the existing array!

I used the following reference from StackOverflow which explains it better than I could: https://stackoverflow.com/a/42428064/2235420

This achieves the data structure you are after:

var localStations = flow.get("dataStations");
// localStations = ( typeof localStations != "undefined" && localStations instanceof Array ) ? localStations : [];

function appendObjTo(thatArray, newObj) {
    const frozenObj = Object.freeze(newObj);
    return Object.freeze(thatArray.concat(frozenObj));
}

function station(station, diesel, e5, e10) {
    return {
        station: station,
        diesel: diesel,
        e5: e5,
        e10: e10
    };
}

// localStations.push({ key: 0, station: station(1, 2, 3, 4) });

const appendedLocalStations = appendObjTo(localStations, { key: 0, station: station(1, 2, 3, 4) })

flow.set("dataStations", appendedLocalStations);

return appendedLocalStations;

image

The flow I used as reference also:

[{"id":"ede3744e.eed578","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"e074d7e.4c4fd28","type":"inject","z":"ede3744e.eed578","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[["40f58adf.654244"]]},{"id":"40f58adf.654244","type":"function","z":"ede3744e.eed578","name":"push to dataStations","func":"var localStations = flow.get(\"dataStations\");\n// localStations = ( typeof localStations != \"undefined\" && localStations instanceof Array ) ? localStations : [];\n\nfunction appendObjTo(thatArray, newObj) {\n    const frozenObj = Object.freeze(newObj);\n    return Object.freeze(thatArray.concat(frozenObj));\n}\n\nfunction station(station, diesel, e5, e10) {\n    return {\n        station: station,\n        diesel: diesel,\n        e5: e5,\n        e10: e10\n    };\n}\n\n// localStations.push({ key: 0, station: station(1, 2, 3, 4) });\n\nconst appendedLocalStations = appendObjTo(localStations, { key: 0, station: station(1, 2, 3, 4) })\n\nflow.set(\"dataStations\", appendedLocalStations);\n\nreturn appendedLocalStations;","outputs":1,"noerr":0,"x":375,"y":100,"wires":[[]]},{"id":"9ec2b03e.3d83d","type":"inject","z":"ede3744e.eed578","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":110,"y":50,"wires":[["a46427b4.825978"]]},{"id":"a46427b4.825978","type":"function","z":"ede3744e.eed578","name":"emptyArray","func":"flow.set(\"dataStations\", new Array());\nreturn msg;","outputs":1,"noerr":0,"x":345,"y":50,"wires":[[]]}]
#8

@knolleary that helps, just change the return msg to "foo" and it works as expected:

dataStations

The best is, I just returned the message for testing reasons this is for the later run even not required :wink:

@RoganFPS thanks for your explanations, I will look into it in detail (specially the shared link looks like having may a lot of interesting information).

But just one think to have it correctly document for others - if the question comes up. The flow.set function adds the variable to the flow context in order to ensure lifetime during the complete flow not only within the function node. Nothing uncommon for my understanding and nothing I invented by myself.

Thanks again for the fabulous support!

Working Code:

var localStations = flow.get("dataStations");
localStations = ( typeof localStations != "undefined" && localStations instanceof Array ) ? localStations : [];

function station(station, diesel, e5, e10) {
    return {
        station: station,
        diesel: diesel,
        e5: e5,
        e10: e10
    };
}

localStations.push({ key: msg.parts.key, station: station(msg.parts.key, msg.payload.diesel, msg.payload.e5, msg.payload.e10) });

flow.set("dataStations", localStations);

return { payload: "foo"};
1 Like
#9

That is not correct. flow.set() does not push (in the Array sense), it adds it to the context and is effectively just a variable in the context pointing to the object being passed.

#10

yes, my assumption was incorrect :slight_smile:

1 Like