Set payload to first occurence of certain key/value in array

Flow:

[
    {
        "id": "1c81edcb527606e1",
        "type": "inject",
        "z": "1151076dd0552f2d",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"status\":\"DELIVERED\",\"dateIso\":\"2023-03-03T10:26:00+01:00\"},{\"status\":\"HANDED_IN\",\"dateIso\":\"2023-02-24T14:10:50+01:00\"},{\"status\":\"DELIVERED\",\"dateIso\":\"2023-01-26T12:42:50+01:00\"},{\"status\":\"IN_TRANSIT\",\"dateIso\":\"2023-01-23T19:28:08+01:00\"},{\"status\":\"PRE_NOTIFIED\",\"dateIso\":\"2023-01-19T09:01:08+01:00\"}]",
        "payloadType": "json",
        "x": 1010,
        "y": 1260,
        "wires": [
            [
                "6660c65d96d28d5c"
            ]
        ]
    },
    {
        "id": "6660c65d96d28d5c",
        "type": "debug",
        "z": "1151076dd0552f2d",
        "name": "debug 28",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1140,
        "y": 1260,
        "wires": []
    }
]

This array has 5 items where 0 is the newest and 4 is the oldest.

As you can see, both item 0 and 2 has a status of DELIVERED.

I want to do two things:

  1. First I want to look for any occurence of DELIVERED in the array. If the array contains a status of DELIVERED in any of the items, the message shall pass through a switch node.
  2. Then, I want to set the payload to the array item with the oldest occurence of DELIVERED. So in this case, the payload should be set to {"status":"DELIVERED","dateIso":"2023-01-26T12:42:50+01:00"} because item 2 is the oldest where DELIVERED appears.

How would I go on about this? This is just an example array - the array may have 10 items where every other item has DELIVERED, for example.

Thanks!

Though I'm sure you could use a JSONata expression to do this, I'd use a function node.

  1. get the length of the array,
  2. set a variable 'oldest_date' to 0
  3. use a 'For loop' to loop thru each of the items and search the status for delivered.
  4. If found, compare the data of that occurrence to the 'oldest_date'
  5. if older, store that date.
  6. when you exit the loop, you will have the oldest data to do with what you want.
1 Like

I know its not low-code - but all can be done in 1 function node.

/* Filter to DELIVERED */
const FilterToDelivered = msg.payload.filter((ArrayEntry) => ArrayEntry.status === 'DELIVERED');

/* Only continue if we have DELIVERED */
if (FilterToDelivered.length > 0){
    
    /* Get Min Timestamp of DELIVERED */
    const MinTimeStamp = Math.min(...FilterToDelivered.map((ArrayEntry) => Date.parse(ArrayEntry.dateIso)))

    /* Now set payload to oldest - using the min Timestamp we got above */
    msg.payload = FilterToDelivered.find((ArrayEntry) => Date.parse(ArrayEntry.dateIso) === MinTimeStamp)

    return msg
}

image

1 Like

Hmm, when implementing this into my flow I get some issues.

The actual path to the object with the array is:

msg.payload.consignmentSet[0].packageSet[0].eventSet

So I changed the code to this:

const FilterToDelivered = msg.payload.consignmentSet[0].packageSet[0].eventSet.status.filter((ArrayEntry) => ArrayEntry.status === 'DELIVERED');

But then I get the following error:

TypeError: Cannot read properties of undefined (reading 'filter')
const FilterToDelivered = msg.payload.consignmentSet[0].packageSet[0].eventSet.status.filter((ArrayEntry) => ArrayEntry.status === 'DELIVERED');

Should be

const FilterToDelivered = msg.payload.consignmentSet[0].packageSet[0].eventSet.filter((ArrayEntry) => ArrayEntry.status === 'DELIVERED');

You were trying to call filter on a property, not the array its self

filter should be applied on an array object, and you provide a delegate to do the filtering:
In our case the item in the array (I called it ArrayEntry) its status property is DELIVERED

Thanks!

Any way to give the function node 2 outputs, where the message is sent to output 1 if DELIVERED exists, and output 2 if it does not? Like a switch node.

Set the function node properties to have 2 outputs (setup tab)
Then...

/* Filter to DELIVERED */
const FilterToDelivered = msg.payload.consignmentSet[0].packageSet[0].eventSet.filter((ArrayEntry) => ArrayEntry.status === 'DELIVERED');

/* Only continue if we have DELIVERED */
if (FilterToDelivered.length > 0){
    
    /* Get Min Timestamp of DELIVERED */
    const MinTimeStamp = Math.min(...FilterToDelivered.map((ArrayEntry) => Date.parse(ArrayEntry.dateIso)))

    /* Now set payload to oldest - using the min Timestamp we got above */
    msg.payload = FilterToDelivered.find((ArrayEntry) => Date.parse(ArrayEntry.dateIso) === MinTimeStamp)

    return [msg, undefined] /* Top output */
} else {
    return [undefined, msg] /* Bottom output */
}

Anymore, you probably want to start using the built in nodes :sweat_smile:

Thank you so much for the help, I have achieved what I want now :slight_smile:

1 Like

Is it always the last 'delivered' entry in the array that you want? If so there is a simpler solution using findLast()

I always want the first one, not the last one.

In that case one can use find()
Something like (untested)

msg.payload = msg.payload.consignmentSet[0].packageSet[0].eventSet.find(
  (ArrayEntry) => ArrayEntry.status === 'DELIVERED'
);
if (typeof msg.payload !="undefined") {
  return msg
}

[Edit] Fixed some typos above

1 Like

To extend on the simpler approach by @Colin and to retain the ability to output to a 2nd pin if non are delivered.

const FirstDelivered = msg.payload.consignmentSet[0].packageSet[0].eventSet.find((ArrayEntry) => ArrayEntry.status === 'DELIVERED');
if (FirstDelivered !== undefined){
    return [{payload:FirstDelivered}, undefined] /* Top output */
} else {
    return [undefined, msg]                      /* Bottom output */
}

I cant remember if Javascript arrays are guaranteed order - I'm sure they are.

I think I would marginally prefer that to be

const FirstDelivered = msg.payload.consignmentSet[0].packageSet[0].eventSet.find((ArrayEntry) => ArrayEntry.status === 'DELIVERED');
if (FirstDelivered !== undefined){
    msg.payload = FirstDelivered
    return [msg, undefined] /* Top output */
} else {
    return [undefined, msg]                      /* Bottom output */
}

or even

const FirstDelivered = msg.payload.consignmentSet[0].packageSet[0].eventSet.find((ArrayEntry) => ArrayEntry.status === 'DELIVERED');
let answer = []
if (FirstDelivered !== undefined){
    msg.payload = FirstDelivered
    answer =  [msg, undefined] /* Top output */
} else {
    answer =  [undefined, msg]                      /* Bottom output */
}
return answer

I prefer just to have one return at the end. Too many times over many years I have seen code added to a function at the end, forgetting that there has already been a return made earlier in the function, so I have a rule that I never do that, even in a simple case like this.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.