Ray of light

Shed some light on arrays or collections in Microsoft Flow

Microsoft Flow arrays and collection

Today I was asked about how arrays (aka collections) work in Microsoft Flow. In this post I will shed some light on arrays and many of the common scenarios that you may face with arrays in Microsoft Flow.

I will start with creating arrays, then I will look at manipulating arrays, but first of all what are arrays?

In most common programming languages you will recognize the term array as a block of related data elements. You could see this as a table or a list list alike data structure.

Creating arrays

I’m going to start with the following table of data.

Brand Color Age Registration Date
Nissan Red 3 01/07/2015
Citroen Green 2 21/09/2016
Audi Blue 4 01/01/2014

I want to create an array like the above table.

In flow I could now create an array using the createArray function.

createArray('Nissan','Citroen','Audi')

Compose action with an array or collection of car brands

I could even take this further and create arrays in arrays.

createArray(createArray('Nissan','Red',3,'01/07/2015'),createArray('Ctiroen','Green',2,'21/09/2016'),createArray('Audi','Blue',4,'01/07/2014'))

Nested Arrays in Json

And I will get an array of arrays

[
  [
    "Nissan",
    "Red",
    3,
    "01/07/2015"
  ],
  [
    "Citroen",
    "Green",
    2,
    "21/09/2016"
  ],
  [
    "Audi",
    "Blue",
    4,
    "01/07/2014"
  ]
]

Now that we know how to create arrays, it is time to do something with all these arrays.

Where do I find arrays in flows?

Within Flow you can find arrays everywhere. The most common place and most visible place is within the Apply to each step. In this step flow will take an array and step through the elements in the array. So If we take a further look at the previous example then a Compose delivering an array can split by an Apply to each step:

Apply to each when stepping through arrays

Running through the Apply to each you will find the separate elements of my array.

Each element in collections looked at in Apply to each

So far this post is all about creating arrays and then stepping through arrays. Now that we have some basic understanding of arrays in Flow it is time to get some real arrays. For this I’m going to create a list in SharePoint with the same data as in the above table.

SharePoint list

Using the Get Items action from the SharePoint connector I can now read the data form this list.

Get Items from a SharePoint list

Looking at the Body of the Get Items action you will see some json as shown below:

{
  "value": [
    {
      "@odata.etag": "\"1\"",
      "ItemInternalId": "1",
      "ID": 1,
      "Title": "Nissan",
      "Colour": {
        "@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedReference",
        "Id": 0,
        "Value": "Red"
      },
      "Colour#Id": 0,
      "Age": 3,
      "Registration_x0020_Date": "2015-01-07",
      "Modified": "2018-07-10T08:36:27Z",
      "Created": "2018-07-10T08:36:27Z",
      "Author": {
      "@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
      "Claims": "i:0#.f|membership|pieter@pieterveenstradev.onmicrosoft.com",
      "DisplayName": "Pieter Veenstra",
      "Email": "pieter@PieterVeenstraMVP.onmicrosoft.com",
      "Picture": "https:/ /pieterveenstraMVP.sharepoint.com/_layouts/15/UserPhoto.aspx?Size=L&AccountName=pieter@PieterVeenstraMVP.onmicrosoft.com",
      "Department": null,
      "JobTitle": null
    },
    "Author#Claims": "i:0#.f|membership|pieter@pieterveenstradev.onmicrosoft.com",
    "Editor": {
       "@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
       "Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
       "DisplayName": "Pieter Veenstra",
       "Email": "pieter@PieterVeenstraMVP.onmicrosoft.com",
       "Picture": "https:/ /pieterveenstraMVP.sharepoint.com/_layouts/15/UserPhoto.aspx?Size=L&AccountName=pieter@PieterVeenstraMVP.onmicrosoft.com",
       "Department": null,
       "JobTitle": null
    },
    "Editor#Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
    "{Identifier}": "Lists%252fCars%252f1_.000",
    "{Link}": "https://pieterveenstradev.sharepoint.com/_layouts/15/listform.aspx?PageType=4&ListId=51c5fad6-6c45-4bc4-8632-556674abe0be&ID=1&ContentTypeID=0x01004819843C002BE14D97CADA4CCA608281",
    "{Name}": "Nissan",
    "{FilenameWithExtension}": "Nissan",
    "{Path}": "Lists/Cars/",
    "{HasAttachments}": false
  },
  {
     "@odata.etag": "\"1\"",
     "ItemInternalId": "2",
     "ID": 2,
     "Title": "Citroen",
     "Colour": {
     "@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedReference",
     "Id": 2,
     "Value": "Green"
   },
   "Colour#Id": 2,
   "Age": 2,
   "Registration_x0020_Date": "2016-09-23",
   "Modified": "2018-07-10T08:37:04Z",
   "Created": "2018-07-10T08:37:04Z",
   "Author": {
   "@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
   "Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
   "DisplayName": "Pieter Veenstra",
   "Email": "pieter@PieterVeenstraMVP.onmicrosoft.com",
   "Picture": "https://pieterveenstraMVP.sharepoint.com/_layouts/15/UserPhoto.aspx?Size=L&AccountName=pieter@PieterVeenstraMVP.onmicrosoft.com",
   "Department": null,
   "JobTitle": null
 },
   "Author#Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
   "Editor": {
     "@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
     "Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
     "DisplayName": "Pieter Veenstra",
     "Email": "pieter@PieterVeenstraMVP.onmicrosoft.com",
     "Picture": "https://pieterveenstraMVP.sharepoint.com/_layouts/15/UserPhoto.aspx?Size=L&AccountName=pieter@PieterVeenstraMVP.onmicrosoft.com",
     "Department": null,
     "JobTitle": null
   },
   "Editor#Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
   "{Identifier}": "Lists%252fCars%252f2_.000",
   "{Link}": "https:/ /pieterveenstradev.sharepoint.com/_layouts/15/listform.aspx?PageType=4&ListId=51c5fad6-6c45-4bc4-8632-556674abe0be&ID=2&ContentTypeID=0x01004819843C002BE14D97CADA4CCA608281",
   "{Name}": "Citroen",
   "{FilenameWithExtension}": "Citroen",
   "{Path}": "Lists/Cars/",
   "{HasAttachments}": false
},
{
"@odata.etag": "\"1\"",
"ItemInternalId": "3",
"ID": 3,
"Title": "Audi",
"Colour": {
"@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedReference",
"Id": 1,
"Value": "Blue"
},
"Colour#Id": 1,
"Age": 4,
"Registration_x0020_Date": "2014-01-01",
"Modified": "2018-07-10T08:37:42Z",
"Created": "2018-07-10T08:37:42Z",
"Author": {
"@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
"Claims": "i:0#.f|membership|pieter@pieterveenstradev.onmicrosoft.com",
"DisplayName": "Pieter Veenstra",
"Email": "pieter@PieterVeenstraMVP.onmicrosoft.com",
"Picture": "https://pieterveenstraMVP.sharepoint.com/_layouts/15/UserPhoto.aspx?Size=L&AccountName=pieter@PieterVeenstraDev.onmicrosoft.com",
"Department": null,
"JobTitle": null
},
      "Author#Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
      "Editor": {
        "@odata.type": "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
        "Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
        "DisplayName": "Pieter Veenstra",
        "Email": "pieter@PieterVeenstraMVP.onmicrosoft.com",
        "Picture": "https://pieterveenstraMVP.sharepoint.com/_layouts/15/UserPhoto.aspx?Size=L&AccountName=pieter@PieterVeenstraDev.onmicrosoft.com",
        "Department": null,
        "JobTitle": null
      },
      "Editor#Claims": "i:0#.f|membership|pieter@pieterveenstraMVP.onmicrosoft.com",
      "{Identifier}": "Lists%252fCars%252f3_.000",
      "{Link}": "https://pieterveenstraMVP.sharepoint.com/_layouts/15/listform.aspx?PageType=4&ListId=51c5fad6-6c45-4bc4-8632-556674abe0be&ID=3&ContentTypeID=0x01004819843C002BE14D97CADA4CCA608281",
      "{Name}": "Audi",
      "{FilenameWithExtension}": "Audi",
      "{Path}": "Lists/Cars/",
      "{HasAttachments}": false
    }
  ]
}

This is where the data in Microsoft Flow can become difficult to understand. The above JSON might need a second look before you really understand what data can be found. json can be difficult to undertsand, especially when a lot of json is thrown at you. Flow is making your life easy here! Use dynamic content and often you can forget about the json, you simply get your SharePoint list columns back. It even helps you with the complexities around lookups, dates and other column types.

Dynamic content with Current Item

Array functions

Time for a step into the deep end. There is a lot more we can do with arrays. I’m going to start with the arrays functions available within Microsoft Flow. You will notice that in the referenced article the term Collections is also used.

Collection function Task
contains Check whether a collection has a specific item.
empty Check whether a collection is empty.
first Return the first item from a collection.
intersection Return a collection that has only the common items across the specified collections.
join Return a string that has all the items from an array, separated by the specified character.
last Return the last item from a collection.
length Return the number of items in a string or array.
skip Remove items from the front of a collection, and return all the other items.
take Return items from the front of a collection.
union Return a collection that has all the items from the specified collections.

Hey, I want to do more! Where is the sort function?

Well remember when we got the data from my SharePoint list? In the Get Items action you can sort your data. So you might want to sort your data when you collect your data.

Get Items advanced options

Also in the Data operations connector there is no sort:

Data Operations in flow

If you need to sort your array however, please have a look at Sort an array or collection in Microsoft Flow

The examples of functions on Arrays

In this section I will look at operations you can do on arrays such as join, select and filter.

Contains

The contains functions checks your array for any values or elements in your collection or array.

Looking at the following 3 examples. The first 2 return false where the last one returns true.

contains(body('Get_items')?['Value'],'Nissan')

contains(outputs('Compose'),'Nissan')

contains(first(outputs('Compose')),'Nissan')

Within the last example I selected a single record form my array before checking with the contains function for my car brand. Where in the first two examples I’m supplying an array of array to the contains function.

Empty

Empty checks the length of the Collection if no items are found in the array then true is returned. Note that this is not the same as comparing an array to a null value.

First item in collections

The First function returns the first item form the collection. See the 3rd example from the contains section above for a real example.

You could also user the construction array[0] to get to the first item, however when the array isn’t defined you will find that your flows will error while first returns a null value.

Intersection

Imagine that you have two arrays and you want to get the items that exist in both collection. and Now you want to find the items that exist in both collections.

So you could for example have two lists with cars. One list is called my cars and one is called insured cars if you now want to find out which of your cars are insured you could use the intersection function.

intersection(body('Get_items')?['Value'],body('Get_items_2')?['Value'])

Join collections

When you have an array of strings and you would like to create a character separated list of strings then you could do this with the join function. Have you ever has a list of users in SharePoint and you wanted to email them all?

join(outputs('Compose'),';')

Last item in collections

The last function is similar the the first function, other than that you get the last element in the array rather than the first one.

Length of collections

The length function gives you the number of elements in a collection. Nothing exciting here.

Skip

With the skip function you can select all elements after a certain position.

skip(outputs('Compose'),2)

Take

The take function is similar to the skip function but now you can collect all the elements up to a certain point in your array.

take(outputs('Compose'),2)

Union

In my earlier examples I had two arrays in my json. One that I manually created and and one that I get from the Get Items action. To merge these arrays into one array you could run the following:

union(body('Get_items')?['Value'],outputs('Compose'))

Did you notice that Flow really doesn’t care that these two arrays are not exactly the same. As Flow will just consider these two arrays to be JSON. It is just gluing the two collections of data together. this is where a sort might be important, although most of the times you will find that you will just want to process each element in your collection and the order really doesn’t matter.

Select

The select action I also want to include here. Although Select is not a function within Microsoft Flow it can help you transform an array of elements.

Select action in flow

Taking an input array and transforming the arrays using a select often helps improve the processing of arrays.

Filter arrays

If you want to filter your array or collection data in Microsoft Flow then please have a look at my filter data in arrays using the select action posts. I that post I’m giving more details on filtering data using the select.

Often it can be wise to filter an array first before pushing it through an apply to each. For performance reasons this is definitely worth a look. It is quite easy to gain multiple minute in the run of a flow.

 

27 thoughts on “Shed some light on arrays or collections in Microsoft Flow

  1. commented on June 19, 2019 by George Mu'ammar

    Thank you so much for this page. I keep returning to it. Just one question though. In your examples you read a SP list and turn it into an array. So your array has field names, for example when you use Select you can search for ‘ID’. But when you create an array using createarray how can you reference a field in the Select?
    Thanks!
    George Mu’ammar

    • commented on June 19, 2019 by Pieter Veenstra

      Hi George,

      You can select the items in an array in two different ways:

      SomeArrayReturningFunction()?[‘FieldName’]

      or

      SomeArrayReturningFunction()?[0] or SomeArrayReturningFunction()?[1]

      The first option means that you need to name the attributes.
      [
      {
      ‘Name’:’Mr X’,
      ‘Age’: 35
      },
      {
      ‘Name’:’Mrs Y’,
      ‘Age’: 25
      }
      ]

      So in the above example you could select the Name of the second person

      variables(‘myArray’)[1]?[‘Name’]

      Does this help?

      • commented on June 19, 2019 by George

        Of course! Perfectly logical if I had thought for a moment 🙂 thanks for your reply and for your necessary blogs. Microsoft Flow would be hardly usable if we had to depend on Microsoft for documentation

      • commented on June 19, 2019 by Pieter Veenstra

        Thank you for your comments.

  2. commented on October 16, 2019 by Mr L S Hodge

    Hi Pieter, something seems to have gone awry with the formatting of this page.

  3. commented on May 12, 2020 by Priyanka

    Hi Pieter,
    I am new to Microsoft Flow and was wondering if the same concept can be applied on Trello ?
    I have been struggling to get the Custom Fields/ PowerUps in Flow and apparently read that PowerUps is an Array of Strings (https://docs.microsoft.com/en-gb/connectors/trello/#card)

    Could you please sare some light on this?

    Thanks !

    • commented on May 12, 2020 by Pieter Veenstra

      Hi Priyanka,

      For all connectors ( including the Trello one) you will find that json is used to move data around. When you start with building flows it is important to understand the json structures.

      You might also find this post helpful:
      https://sharepains.com/2019/09/16/query-json-in-microsoft-flow/

      Are you able to share the output that you are getting from the Trello actions? Once you have identified the data that you are after in there then querying the data should be quite easy with a compose action.

  4. commented on June 12, 2020 by Raj

    Hi,
    Could you please let me know how we can dynamically combine arrays which are like this into one array list.

    [{“Dato “:”12.07.2020”,”Navn “:”Namma\n Employee 3″,”Jubilæum”:10,”Titel”:””,”Økonomisk kontorsted “:””,”Arbejdssted “:” “,”Privatadresse”:””,”Nærmeste leder “:null}]

    [{“Dato “:”12.07.2020”,”Navn “:”Test\n Employee 4″,”Jubilæum”:10,”Titel”:”Test->Senior Partner”,”Økonomisk kontorsted “:”TEST”,”Arbejdssted “:” “,”Privatadresse”:””,”Nærmeste leder “:null}]

    [{“Dato “:”12.07.2020”,”Navn “:”James\n Doe”,”Jubilæum”:10,”Titel”:”Trainee”,”Økonomisk kontorsted “:”TEST”,”Arbejdssted “:”Kystvejen 29 8000 Aarhus C Denmark”,”Privatadresse”:”Testvej 123 8000 Aarhus C DK”,”Nærmeste leder “:”ner”}]

    [{“Dato “:”12.07.2020”,”Navn “:”Test\n Employee 6″,”Jubilæum”:10,”Titel”:”Trainee”,”Økonomisk kontorsted “:”TEST”,”Arbejdssted “:” “,”Privatadresse”:”Testvej 123 8000 Aarhus C DK”,”Nærmeste leder “:null}]

    • commented on June 12, 2020 by Pieter Veenstra

      Hi Raj,

      You could consider converting the json objects into text then remove the [ and the ].

      then concatenate the texts.

      then add the [ and the ] again and convert it back to a json object with the json function.

      Does that help?

      • commented on June 12, 2020 by Raj

        Hi,
        That is not working well. it was supposed to work fine, my main issue was to actually append tables

        since the main data was in array i am converting the array to string and appending the array. that is not working fine,
        so tried to create the tabular format and append it to array.

        that is multiplying the tables instead of combining all the data into one table.

      • commented on June 12, 2020 by Pieter Veenstra

        Just so that I understand it correctly you have 4 array variable of 1 item each. will you know how many arrays you have? I.e. you always have 4?
        And every array has always got 1 item in it?

      • commented on June 12, 2020 by Rajashekar Regati

        it is dynamic . so we will not be knowing whether it is 1 or 2
        could you please let me know if there is any formula to append [ in the first letter and ] at the last .

      • commented on June 12, 2020 by Pieter Veenstra

        The concat function will do that.

        https://docs.microsoft.com/en-us/azure/logic-apps/workflow-definition-language-functions-reference#concat

      • commented on June 15, 2020 by Rajashekar Regati

        That worked well . i am able to consolidate all the data by first using select in the for loop and then setting up a variable to store all the json data at the end of the for loops and editing the json data to clear ][ brackets and sending the data to create table and triggering an email .

        that sent all the information of the workers dynamically.

  5. commented on July 19, 2020 by Jonathan Smith

    HI Pieter,

    As others have said a really helpful article. Can I ask for help? I have the following JSON returned from a graph query (reading an .xlsm file hence graph being used!!):

    {
    “@odata.context”: “https://graph.microsoft.com/v1.0/$mxxxxxxxxxxx’)/rows”,
    “value”: [
    {
    “@odata.id”: “/drives(‘b%21mxxxxxxxxxx)/rows/itemAt(index=0)”,
    “index”: 0,
    “values”: [
    [
    “Storage HW”,
    3649800,
    375980,
    455928,
    501520.8,
    551672.88,
    5534901.68
    ]
    ]
    },

    After parsing the JSON I can’t select a specific item in the “values array”! i can get the whole “values” array back for a specific “value” index, but really want to get each value so I can align each to a variable. So far the only way I have found is to convert to a string and then use split recursively to get each of the 7 values! Not super efficient, hence asking you as I’m sure I’m just being a twit with my expression!

    Thanks in advance.

    • commented on July 20, 2020 by Pieter Veenstra

      Hi Jonathan,

      The values section is an array in an array.

      you can select the following bit of data with the first() function.

      [
      “Storage HW”,
      3649800,
      375980,
      455928,
      501520.8,
      551672.88,
      5534901.68
      ]

      Then to select the items you can use [0] or [1] the select the 1st or 2nd item.

      You could even consider pushing the values array into a select action. Then you can map those values and give each value a name. That makes querying them just that little bit more friendly.

  6. commented on July 20, 2020 by Jonathan Smith

    Hi Pieter

    Thanks for getting back so quickly. What you said in your comment was what I though I would be able to do, however I just can’t get it work! (apologies for the long post coming!).

    From the parsed JSON I did a compose to get the first “values:” item which gave me:

    {
    “@odata.id”: “/drives(‘b%21m9wlSvyeUUeZpdshk2qUt50nqlCqVkdMt7WDTh4Ejtiv-Tfa-uNCSqOCCxyi4otz’)/items(‘016HPV57IUWMU57UH3PFEJPQ763ICUZGWH’)/workbook/tables(%27%7B9E25A436-A9A2-46D0-8BD6-91A01420BB71%7D%27)/rows/itemAt(index=1)”,
    “index”: 1,
    “values”: [
    [
    “Storage HW Mnt”,
    1843160,
    152732.8,
    141250.56,
    103583.744,
    56971.0592,
    2297698.1632
    ]
    ]
    }

    I then set a second compose action to just pull the first item within “values:” i.e. “Storage HW Mnt”. I used the following expression:

    first(outputs(‘Compose_2’))

    But got the error:

    InvalidTemplate. Unable to process template language expressions in action ‘Compose’ inputs at line ‘1’ and column ‘2267’: ‘The template language function ‘first’ expects its parameter be an array or a string. The provided value is of type ‘Object’. Please see https://aka.ms/logicexpressions#first for usage details.’.

    When I tried to be specific and did

    outputs(‘Compose_2’)?[‘values’][0]

    I just get:

    [
    “Storage HW Mnt”,
    1843160,
    152732.8,
    141250.56,
    103583.744,
    56971.0592,
    2297698.1632
    ]

    Would really appreciate if you could guide me on the syntax as MS docs don’t help and googling does return any similar example 🙁

    Thanks again.

  7. commented on July 20, 2020 by Jonathan Smith

    Pieter,

    sadly it says that is an invalid expression so can’t save and run.

  8. commented on July 20, 2020 by Jonathan Smith

    Pieter,

    just for reference the JSON schema looks like:

    {
    “type”: “object”,
    “properties”: {
    “@@odata.context”: {
    “type”: “string”
    },
    “value”: {
    “type”: “array”,
    “items”: {
    “type”: “object”,
    “properties”: {
    “@@odata.id”: {
    “type”: “string”
    },
    “index”: {
    “type”: “integer”
    },
    “values”: {
    “type”: “array”,
    “items”: {
    “type”: “array”
    }
    }
    },
    “required”: [
    “@@odata.id”,
    “index”,
    “values”
    ]
    }
    }
    }
    }

    • commented on July 20, 2020 by Pieter Veenstra

      I set a compose action to the following json

      {
      "odata.context": "https://graph.microsoft.com/v1.0/$mxxxxxxxxxxx’)/rows",
      "value": [
      {
      "odata.id": "/drives('b%21mxxxxxxxxxx)/rows/itemAt(index=0)",
      "index": 0,
      "values": [
      [
      "Storage HW",
      3649800,
      375980,
      455928,
      501520.8,
      551672.88,
      5534901.68
      ]
      ]
      }
      ]
      }

      then in a second compose I used the following expression:

      first(first(outputs(‘compose’)?[‘value’])?[‘values’])?[0]

      And this resulted in the expected fields being returned.

  9. commented on July 21, 2020 by Jonathan Smith

    Pieter, thanks so much for the help with the syntax, as soon as I get of my 5th zoom of the day I will try your code! ;o)

  10. commented on July 21, 2020 by Jonathan Smith

    Pieter,

    Humph, so I tried exactly the same as you did above and I can’t even get that to work just tells me the expression for the second compose is invalid! Something seems horribly wrong this end.

  11. commented on July 21, 2020 by Jonathan Smith

    Sorry Pieter, was me! typo. working all good, thank you again.

  12. Hi,

    I came around here to find a solution for my problem, it is really useful, I’m not going to lie.

    However, I have a small problem with my current scenario (it is a bit weird for some people).

    I tried to create a very simple array (for storing weighted score that has 4 score in each question that answered in Microsoft Forms). I already created logic behind. However, when I want to add new item into array, I don’t really know how to do so.

    For example:

    I have empty array in “Result”, and I want to include weights, which are 0, 0.33, 0.66, and 1. There is 70 questions in the surveys.

    Expected results should be something like this:

    [0.66, 0.66, 0.33, …, 1]

    Is it possible to do so? If not, what is the best possible way to do this?

    • commented on July 25, 2020 by Pieter Veenstra

      Hi Kittanan,

      As you are having a set length array i would go for the easy option

      In the image below refer to each element in the array using the code below

      outputs('Compose')[0]

      Edit an array

      this will then result in the following flow run:

      Result array

      So I inserted a value in the middle of an array.

      Does this help?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: