Includes function in javascript not working properly inside Javascript Policy

Consider following Json as response from target(backend) API:

{
    "key1": "data1",
    "key2": "data2",
    "key3": "data3",
    "data": [
        {
            "key1_1": "data1_1",
            "key1_2": "data1_2",
            "key1_3": "data1_3",
            "key1_4": "data1_4",
            "key1_5": "data1_5",
            "key1_6": "data1_6",
            "key1_7": "data1_7"
        },
        {
            "key1_1": "data2_1",
            "key1_2": "data2_2",
            "key1_3": "data2_3",
            "key1_4": "data2_4",
            "key1_5": "data2_5",
            "key1_6": "data2_6",
            "key1_7": "data2_7"
        },
        {
            "key1_1": "data3_1",
            "key1_2": "data3_2",
            "key1_3": "data3_3",
            "key1_4": "data3_4",
            "key1_5": "data3_5",
            "key1_6": "data3_6",
            "key1_7": "data3_7"
        }
    ]
} 

The above response is captured in Javascript Policy(js script) to modify(customize) the response sent back to different client apps consuming this API Proxy based on each app's custom attribute.

Below is the code written in javascript to achieve the expected result.

var target_response = context.getVariable('message.content')
var client_app_allowed_fields = context.getVariable('allowed_fields')


var allowed_fields = client_app_allowed_fields.split(',')


// print(allowed_fields) => key1_2,key1_3,key1_4,key1_6(This value comes from custom attribute of the cleint which made the request)
// print(typeof allowed_fields) => object
// print(JSON.stringify(allowed_fields)) => ["key1_2","key1_3","key1_4","key1_6"]


var input_data = JSON.parse(target_response)


if(input_data.data.length)
{
    for(var i=0; i<input_data.data.length; i++)
    {
        for (var key in input_data.data[i])
        {
            if(!(allowed_fields.includes(key)))
            {
               delete input_data.data[i][key]
            }
        }
    }
}
// If response from target API have one entry, data will populated directly as key & value pair(below code is to handle those scenario)
else
{
    for (var key in input_data.data)
    {
        if(!(allowed_fields.includes(key)))
        {
            delete input_data.data[key];
        }
    }
}


var output = JSON.stringify(input_data);
context.setVariable('message.content',output)

But getting below error from apigee, but works fine, when tried the above code outside apigee

{
    "fault": {
        "faultstring": "Execution of intercept-response failed with error: Javascript runtime error: \"TypeError: Cannot find function includes in object key1_2,key1_3,key1_4,key1_6. (intercept-response.js:19)\"",
        "detail": {
            "errorcode": "steps.javascript.ScriptExecutionFailed"
        }
    }
}

Apigee says "Cannot find function includes in object", this is due to any error in the code or this is specify to apigee.

Also whenever we try to use negation(!) or not equal to(!=) operators in the logic, we are not getting expected results. Also I am new to javascript.

Below is the expected result

{
    "key1": "data1",
    "key2": "data2",
    "key3": "data3",
    "data": [
        {
            "key1_1": "data1_1",
            "key1_5": "data1_5",
            "key1_7": "data1_7"
        },
        {
            "key1_1": "data2_1",
            "key1_5": "data2_5",
            "key1_7": "data2_7"
        },
        {
            "key1_1": "data3_1",
            "key1_5": "data3_5",
            "key1_7": "data3_7"
        }
    ]
}
Solved Solved
1 2 10.2K
1 ACCEPTED SOLUTION

The Javascript you can run within node / v8 has a function, includes(), on all arrays, which returns a boolean, indicating whether the element is present in the array or not.

Apigee doesn't use node/v8 for its Javascript engine. Apigee uses Rhino. (I think the version of Rhino used within Apigee is v1.7.7). Rhino does not include the .includes() function on arrays. This is hwy you observed the difference in behavior (eg, "it works fine outside of Apigee.")

But no problem, in Rhino you can use .indexOf(). This is what it looks like:

var allowed_fields = context.getVariable('allowed_fields').split(',');
var input_data = JSON.parse(context.getVariable('message.content'));

function isAllowedField(key) {
  // Array.indexOf(element) returns -1 if 
  // the element is not present in the array.
  return allowed_fields.indexOf(key) != -1;
}

function filterData(data) {
  for (var key in data) {
    if(!isAllowedField(key)) {
      delete data[key];
    }
  }
}

if(input_data.data.length) {
  // response contains an array
  input_data.data.forEach(filterData);
}
else {
  // response contains just one entry
  filterData(input_data.data);
}

var output = JSON.stringify(input_data, null, 2);
context.setVariable('message.content', output);
//print(output);

You can see i refactored it a bit to use a couple helper functions.

When I try this, my output is:

{
  "key1": "data1",
  "key2": "data2",
  "key3": "data3",
  "data": [
    {
      "key1_2": "data1_2",
      "key1_3": "data1_3",
      "key1_4": "data1_4",
      "key1_6": "data1_6"
    },
    {
      "key1_2": "data2_2",
      "key1_3": "data2_3",
      "key1_4": "data2_4",
      "key1_6": "data2_6"
    },
    {
      "key1_2": "data3_2",
      "key1_3": "data3_3",
      "key1_4": "data3_4",
      "key1_6": "data3_6"
    }
  ]
}

....which seems right, though it is not what you showed.

Your list of fields is called "allowed fields" , so I assumed those were the fields you wanted to KEEP in the response. The "expected result" showed the opposite. (retaining data1_1, data1_5, data1_7 ...)

If you want that, it's a matter of reversing the logic. Maybe consider changing the name of the context variable too, if you do that.

View solution in original post

2 REPLIES 2

The Javascript you can run within node / v8 has a function, includes(), on all arrays, which returns a boolean, indicating whether the element is present in the array or not.

Apigee doesn't use node/v8 for its Javascript engine. Apigee uses Rhino. (I think the version of Rhino used within Apigee is v1.7.7). Rhino does not include the .includes() function on arrays. This is hwy you observed the difference in behavior (eg, "it works fine outside of Apigee.")

But no problem, in Rhino you can use .indexOf(). This is what it looks like:

var allowed_fields = context.getVariable('allowed_fields').split(',');
var input_data = JSON.parse(context.getVariable('message.content'));

function isAllowedField(key) {
  // Array.indexOf(element) returns -1 if 
  // the element is not present in the array.
  return allowed_fields.indexOf(key) != -1;
}

function filterData(data) {
  for (var key in data) {
    if(!isAllowedField(key)) {
      delete data[key];
    }
  }
}

if(input_data.data.length) {
  // response contains an array
  input_data.data.forEach(filterData);
}
else {
  // response contains just one entry
  filterData(input_data.data);
}

var output = JSON.stringify(input_data, null, 2);
context.setVariable('message.content', output);
//print(output);

You can see i refactored it a bit to use a couple helper functions.

When I try this, my output is:

{
  "key1": "data1",
  "key2": "data2",
  "key3": "data3",
  "data": [
    {
      "key1_2": "data1_2",
      "key1_3": "data1_3",
      "key1_4": "data1_4",
      "key1_6": "data1_6"
    },
    {
      "key1_2": "data2_2",
      "key1_3": "data2_3",
      "key1_4": "data2_4",
      "key1_6": "data2_6"
    },
    {
      "key1_2": "data3_2",
      "key1_3": "data3_3",
      "key1_4": "data3_4",
      "key1_6": "data3_6"
    }
  ]
}

....which seems right, though it is not what you showed.

Your list of fields is called "allowed fields" , so I assumed those were the fields you wanted to KEEP in the response. The "expected result" showed the opposite. (retaining data1_1, data1_5, data1_7 ...)

If you want that, it's a matter of reversing the logic. Maybe consider changing the name of the context variable too, if you do that.

Hi @dchiesa1,

Thanks a lot for solution, it helped a lot 😊