189
Views
13
Comments
Solved
How can I iterate over the fields of an input stucture in a C# extension

I am creating a C# extension to transform a list of fields to a text payload in a specific format. 

The list of fields looks like this in JSON

{

"merchant_id":"00000000",

"merchant_key":"46f0dfg94581a",

"return_url" : "https://www.example.com/success",

"cancel_url" : "https://www.example.com/cancel",

"notify_url" : "https://www.example.com/notify",

"amount":100.0,

"item_name":"Product 1"

}

and the structure in Integration Studio looks like this 



I am looking for C# code to do the following: 

for each field in Payload
currentField = field name
currentValue = field value
    output = output + "{field name}={field value}&"

so the output will be "merchant_id=00000000&merchant_key=46f0dfg94581a..." etc

In other words, I want some way to iterate over the key-value pairs of the JSON, i.e. each attribute name and value in the structure. If an input structure is not the way to do this, I am open to other suggestions. I need the "key" and "value" separate as I need to do additional processing on the value.


We have got it working by assigning each field of the structure to a class we can iterate, but this is not ideal as you have to assign each field and there will be many more coming.


Here is a look at the action I have created in Integration Studio


Thanks in advance for any effort to help!


BTW if there is a way to do something with native outsystems I would love to know. I would like the starting point to be a structure that I convert to iterate over the field name and value for all fields. 

2024-09-08 11-13-40
Nuno Damaso
 
MVP
Solution

Hi Mitchell,


Directly addressing your issue and code, you can in fact get it through reflection like you are trying to do.

You just need to access the actual structure (ssSTPayload) within the record. You will also need to exclude the "OptimizedAttributes" attribute that is injected by default.


You can try this (and adjusting accordingly your outputs and string building, etc):


           // Your Current, or simillar

            var outputString = string.Empty;

            foreach (var f in payload.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))

            {

                outputString += $"Name: {f.Name} | Value: {f.GetValue(payload)}\n";

            }


            //Try this

            var testOutputString = string.Empty;

            foreach (var f in payload.ssSTPayload.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))

            {

                if (!f.Name.Equals("OptimizedAttributes"))

                {

                    testoutputString += $"Name: {f.Name} | Value: { f.GetValue(payload.ssSTPayload)}\n";

                }

            }


The above being said, you could also provide as an input, a List of Key-Value Records, each one representing an attribute of your payload. 

Your "Payload" input would be something like:

Payload

 DataType: RecordList, RecordDefinition: KeyVaueRecord

KeyValueRecord:

  Attributes:

    Key: Text

    Value: Test

Representation: [{Key: "merchant_id", Value: "something"}, {Key: "merchant_key": Value: "something"} etc..]

This would make it easier on the extension to just iterate the list and pull each Key Value, completely dynamic. 

It also makes it easier on the record definition as you mention that "(...)there will be many more coming".


There is of course, the trade-off that you have to prepare the "Payload" list of Key Value Records before hand on Outsystems.


I will suggest you the following article if you don't mind, from Outsystems MVP João Marques: 

https://medium.com/@jsmarques13/integrating-dynamic-structures-with-outsystems-6c45e36a4d47


Hope it helps,

Nuno

2017-04-20 10-11-42
Mitch JT

Thanks Nuno! This looks like it should get me there. I will come back and mark this as the solution once confirmed :)

UserImage.jpg
Samuel Pernas

Hi Nuno, 

I saw this topic and it was very useful for me, but know i have found a problem i do not undestand why is happening, and i hope you can help me.

I have one structure with 3 attributes, in the future i plan to add or change the name of the attributes. I've created an action with these inputs: InRecord, ColumnName, OutRecord.


In this scenario, i will receive a record with attributes that are true or false and the name of one of those attributes. Given the name, i want to set that value to the opposite of the actual value. For example: InEmployee.Id = not InEmployee.id.

For this i have an OutPut record of the same type where i want the definitive record. 

This is my code:

        public void MssActiveColumns(RCEmployeeRecord ssInEmployee, string ssColumnName, out RCEmployeeRecord ssOutEmployee) {

            ssColumnName = "ss" + ssColumnName;

            ssOutEmployee = ssInEmployee; <- Copy of the actual record

            FieldInfo []StructureFields = ssOutEmployee.ssSTEmployee.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 

            foreach (var field in StructureFields)

            {

                if (!field.Name.Equals("OptimizedAttributes"))

                {

                    if(field.Name.Equals(ssColumnName))

                    {

                        var IsActive = ! (bool) field.GetValue(ssOutEmployee.ssSTEmployee); <- This works

                        field.SetValue(ssOutEmployee.ssSTEmployee, IsActive);

                    }

                }

            }

        } // MssActiveColumns


The problem is that when i do the 'field.SetValue' does not change the value.  I can read the value but not change it, and i dont know what am i doing wrong.

Log says 'var IsActive = False/True' depends of the original value, but SetValue does not change it.

Thanks!

2024-09-08 11-13-40
Nuno Damaso
 
MVP


Hi Samuel,

Please try this:

        public void MssActiveColumns(RCEmployeeRecord ssInEmployee, string ssColumnName, out RCEmployeeRecord ssOutEmployee) {
            ssColumnName = "ss" + ssColumnName;
            ssOutEmployee = ssInEmployee; <- Copy of the actual record
            FieldInfo []StructureFields = ssOutEmployee.ssSTEmployee.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            //*****************BOXING********************//
             var boxedEmployeeStruct = (object) ssOutEmployee .ssSTEmployee;
            //*****************BOXING********************//

            foreach (var field in StructureFields)
            {
                if (!field.Name.Equals("OptimizedAttributes"))
                {
                    if(field.Name.Equals(ssColumnName))
                    {
                        var IsActive = ! (bool) field.GetValue(boxedEmployeeStruct ); <- This works
                        field.SetValue(boxedEmployeeStruct , IsActive);
                    }
                }
            }

            //*****************UNBOXING********************//            
             ssOutEmployee.ssSTEmployee = (STRCEmployeeStructure)boxedEmployeeStruct ;
            //*****************UNBOXING********************//

        } // MssActiveColumns 


The reason for this are mutable structs, used by Outsystems for the underlying extension types. 

When SetValue is applied, it is creating a new object(passing by value because of the struct) instead of referencing it (in a referenced type). So you are in fact setting the value, just not of your initial struct property, but of a newly created one at that point.

By BOXING it into an object(passed by reference) you can work and then UNBOX it back to the struct.


I will also suggest you to take an approach to the whole thing of providing a key-value pair list input (where key is the column name, value its value), it makes things easier and less error prone.

Here's an article (posted above) to address some techniques to achieve this, from Outsystems MVP João Marques:

https://medium.com/@jsmarques13/integrating-dynamic-structures-with-outsystems-6c45e36a4d47

UserImage.jpg
Samuel Pernas

Thank You so much! This works perfectly.

I knew that the input parameters are a copy of the original variable, but i didn't know that SetValue with one of the parameters creates a new Object.

Now that it works i'll upgrade the code and add the Key:Value structure, but first i wanted it to work properly.

Again, thank you for your help!

2019-07-01 07-16-04
Vinod Patidar

Hi Mitchell,

In C# extensions you can use for each loop like -

foreach (var item in RCPayloadRecord)

{

}

Thanks

Vinod

2017-04-20 10-11-42
Mitch JT

Thanks Vinod,

I was hoping it would be so simple, but alas I seem to get the error "foreach statement cannot operate on variables of type 'RCPayloadRecord'.... does not contain a public instance definition for 'GetEnumerator'

2019-07-01 07-16-04
Vinod Patidar

Hi Mitchell,

Select the record list from sspfPayload. Put the Dot(.) after sspfPayload then you will get the child object.

Like -

foreach (var item in sspfPayload.ChildObject)

{

}

Please share the Extension Project.

Thanks

Vinod

2017-04-20 10-11-42
Mitch JT

I am dealing with a single record in this case. Please see my extension 

PfHelper.xif
2019-07-01 07-16-04
Vinod Patidar

You can get using below code -

string itemName = sspfPayload.ssSTPayload.ssitem_name;

string merchantId = sspfPayload.ssSTPayload.ssmerchant_id;


Thanks

Vinod

2017-04-20 10-11-42
Mitch JT

Thanks for your help, but this does not answer my question. I have already solved the problem by assigning each value explicitly. I would like a solution that solves it with a loop.


2019-07-01 07-16-04
Vinod Patidar

Hi Mitchell,

First you need to change data type for pfPayload input parameter from Record to Record List.


Then you can iterate it using below code -

 foreach (RCPayloadRecord item in sspfPayload)

            {

               string itemName = item.ssSTPayload.ssitem_name;

                string merchantId = item.ssSTPayload.ssmerchant_id;


            }

2017-04-20 10-11-42
Mitch JT

Sorry Vinod this is not like what I want to do. I'm not sure you have understood my problem statement. Thanks for the help though!


2024-09-08 11-13-40
Nuno Damaso
 
MVP
Solution

Hi Mitchell,


Directly addressing your issue and code, you can in fact get it through reflection like you are trying to do.

You just need to access the actual structure (ssSTPayload) within the record. You will also need to exclude the "OptimizedAttributes" attribute that is injected by default.


You can try this (and adjusting accordingly your outputs and string building, etc):


           // Your Current, or simillar

            var outputString = string.Empty;

            foreach (var f in payload.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))

            {

                outputString += $"Name: {f.Name} | Value: {f.GetValue(payload)}\n";

            }


            //Try this

            var testOutputString = string.Empty;

            foreach (var f in payload.ssSTPayload.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))

            {

                if (!f.Name.Equals("OptimizedAttributes"))

                {

                    testoutputString += $"Name: {f.Name} | Value: { f.GetValue(payload.ssSTPayload)}\n";

                }

            }


The above being said, you could also provide as an input, a List of Key-Value Records, each one representing an attribute of your payload. 

Your "Payload" input would be something like:

Payload

 DataType: RecordList, RecordDefinition: KeyVaueRecord

KeyValueRecord:

  Attributes:

    Key: Text

    Value: Test

Representation: [{Key: "merchant_id", Value: "something"}, {Key: "merchant_key": Value: "something"} etc..]

This would make it easier on the extension to just iterate the list and pull each Key Value, completely dynamic. 

It also makes it easier on the record definition as you mention that "(...)there will be many more coming".


There is of course, the trade-off that you have to prepare the "Payload" list of Key Value Records before hand on Outsystems.


I will suggest you the following article if you don't mind, from Outsystems MVP João Marques: 

https://medium.com/@jsmarques13/integrating-dynamic-structures-with-outsystems-6c45e36a4d47


Hope it helps,

Nuno

2017-04-20 10-11-42
Mitch JT

Thanks Nuno! This looks like it should get me there. I will come back and mark this as the solution once confirmed :)

UserImage.jpg
Samuel Pernas

Hi Nuno, 

I saw this topic and it was very useful for me, but know i have found a problem i do not undestand why is happening, and i hope you can help me.

I have one structure with 3 attributes, in the future i plan to add or change the name of the attributes. I've created an action with these inputs: InRecord, ColumnName, OutRecord.


In this scenario, i will receive a record with attributes that are true or false and the name of one of those attributes. Given the name, i want to set that value to the opposite of the actual value. For example: InEmployee.Id = not InEmployee.id.

For this i have an OutPut record of the same type where i want the definitive record. 

This is my code:

        public void MssActiveColumns(RCEmployeeRecord ssInEmployee, string ssColumnName, out RCEmployeeRecord ssOutEmployee) {

            ssColumnName = "ss" + ssColumnName;

            ssOutEmployee = ssInEmployee; <- Copy of the actual record

            FieldInfo []StructureFields = ssOutEmployee.ssSTEmployee.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 

            foreach (var field in StructureFields)

            {

                if (!field.Name.Equals("OptimizedAttributes"))

                {

                    if(field.Name.Equals(ssColumnName))

                    {

                        var IsActive = ! (bool) field.GetValue(ssOutEmployee.ssSTEmployee); <- This works

                        field.SetValue(ssOutEmployee.ssSTEmployee, IsActive);

                    }

                }

            }

        } // MssActiveColumns


The problem is that when i do the 'field.SetValue' does not change the value.  I can read the value but not change it, and i dont know what am i doing wrong.

Log says 'var IsActive = False/True' depends of the original value, but SetValue does not change it.

Thanks!

2024-09-08 11-13-40
Nuno Damaso
 
MVP


Hi Samuel,

Please try this:

        public void MssActiveColumns(RCEmployeeRecord ssInEmployee, string ssColumnName, out RCEmployeeRecord ssOutEmployee) {
            ssColumnName = "ss" + ssColumnName;
            ssOutEmployee = ssInEmployee; <- Copy of the actual record
            FieldInfo []StructureFields = ssOutEmployee.ssSTEmployee.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            //*****************BOXING********************//
             var boxedEmployeeStruct = (object) ssOutEmployee .ssSTEmployee;
            //*****************BOXING********************//

            foreach (var field in StructureFields)
            {
                if (!field.Name.Equals("OptimizedAttributes"))
                {
                    if(field.Name.Equals(ssColumnName))
                    {
                        var IsActive = ! (bool) field.GetValue(boxedEmployeeStruct ); <- This works
                        field.SetValue(boxedEmployeeStruct , IsActive);
                    }
                }
            }

            //*****************UNBOXING********************//            
             ssOutEmployee.ssSTEmployee = (STRCEmployeeStructure)boxedEmployeeStruct ;
            //*****************UNBOXING********************//

        } // MssActiveColumns 


The reason for this are mutable structs, used by Outsystems for the underlying extension types. 

When SetValue is applied, it is creating a new object(passing by value because of the struct) instead of referencing it (in a referenced type). So you are in fact setting the value, just not of your initial struct property, but of a newly created one at that point.

By BOXING it into an object(passed by reference) you can work and then UNBOX it back to the struct.


I will also suggest you to take an approach to the whole thing of providing a key-value pair list input (where key is the column name, value its value), it makes things easier and less error prone.

Here's an article (posted above) to address some techniques to achieve this, from Outsystems MVP João Marques:

https://medium.com/@jsmarques13/integrating-dynamic-structures-with-outsystems-6c45e36a4d47

UserImage.jpg
Samuel Pernas

Thank You so much! This works perfectly.

I knew that the input parameters are a copy of the original variable, but i didn't know that SetValue with one of the parameters creates a new Object.

Now that it works i'll upgrade the code and add the Key:Value structure, but first i wanted it to work properly.

Again, thank you for your help!

Community GuidelinesBe kind and respectful, give credit to the original source of content, and search for duplicates before posting.