Skip to content

Commit

Permalink
Sc 85635/error when clicking on first step of lead (#106)
Browse files Browse the repository at this point in the history
* fix JSON response parsing to wrap numeric keys in quotes
  • Loading branch information
cgrayson authored Jan 29, 2025
1 parent 13edaaa commit 0b64434
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 10 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [2.23.6] - 2025-01-28
### Fixed
- fixed JSON response parsing to wrap numeric keys in quotes ([sc-85635](https://app.shortcut.com/active-prospect/story/85635/error-when-clicking-on-first-step-of-lead-details))
- minor package updates & a few lint fixes

## [2.23.4] - 2023-01-30
### Added
- added support for `PATCH` requests for JSON and XML integrations ([sc-47206](https://app.shortcut.com/active-prospect/story/47206/add-support-for-patch-to-custom-integrations))
Expand Down
28 changes: 27 additions & 1 deletion lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,33 @@ const dotWild = require('dot-wild');

const { ensureArray, inverseOutcome, toRegex } = require('./helpers');

/**
* Elsewhere in the LeadConduit handler this appended data may go through
* the `flat` module's flatten() and/or unflatten(). In cases where this JSON
* has numeric keys, especially large ones, this can cause problems as
* unflatten() can treat them as array indexes.
* E.g., { foo: { 1984: { id: 1984, name: 'Van Halen' } } } could wind up
* allocating an array for foo with 1,983 null elements, then that object at
* foo[1984]. This function prevents that by forcing those keys to strings:
* { foo: { '1984': { id: 1984, name: 'Van Halen' } } }
*/
const quoteNumericAttributes = function (obj) {
if (!_.isPlainObject(obj)) { return obj; }
const newObj = {};
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const keyAsNumber = Number.parseInt(key);
if (Number.isNaN(keyAsNumber)) {
newObj[key] = quoteNumericAttributes(obj[key]);
} else {
newObj[`'${key}'`] = quoteNumericAttributes(obj[key]);
}
}
}
return newObj;
};

/* eslint-disable-next-line complexity */
const response = function(vars, req, res) {

let cookie, outcome, price;
Expand Down Expand Up @@ -128,7 +154,7 @@ const response = function(vars, req, res) {
catch (error6) {}
} else if (_.isPlainObject(doc)) {
// This is a JSON object
event = doc;
event = quoteNumericAttributes(doc);
} else if (_.isPlainObject(vars.capture)) {
// Use any "capture" variables to capture parts of the text into properties from unstructured text
if (_.isFunction(doc.html)) { doc = doc.html(); }
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"ipaddr.js": "^2.0.1",
"is-valid-domain": "^0.1.6",
"lodash": "^4.0.0",
"mime-content": ">=0.0.7",
"mime-content": "~0.0.10",
"mimeparse": "*",
"private-ip": "^2.3.3",
"regex-parser": "^2.2.1",
Expand All @@ -30,7 +30,7 @@
"xmlbuilder": "^7.0.0"
},
"devDependencies": {
"@activeprospect/integration-dev-dependencies": "^1.1.11",
"@activeprospect/integration-dev-dependencies": "^2.0.1",
"leadconduit-types": "^4.5.0"
}
}
40 changes: 33 additions & 7 deletions test/response-spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { assert } = require('chai');
const xmlbuilder = require('xmlbuilder');
const response = require('../lib/response');
const flat = require('flat');
const { isArray } = require('lodash');

describe('Response', function() {

Expand Down Expand Up @@ -516,12 +518,41 @@ describe('Response', function() {
const expected = {
outcome: 'success',
price: 0,
'0': {
"'0'": {
foo: 'foo'
}
};
assert.deepEqual(response({}, {}, json({ '0': { foo: 'foo' }})), expected);
});

it('should quote numeric JSON keys', () => {
const jsonResponse = {
"buyers": [{"name": "Buyer 1"}, {"name": "Buyer 2"}],
"foo": {
"42": {
"id": 42
}
}
};
const expected = {
"buyers": [{"name": "Buyer 1"}, {"name": "Buyer 2"}],
"foo": {
"'42'": {
"id": 42
}
},
"outcome": "success",
"price": 0
};
const actual = response({}, {}, json(jsonResponse));
assert.deepEqual(actual, expected);

// also put the result through flatten() and unflatten() to make sure
// the numeric key doesn't get treated as an array index
const flatUnflat = flat.unflatten(flat.flatten(actual));
assert.isFalse(isArray(flatUnflat.foo));
assert.deepEqual(flatUnflat, expected);
});
});


Expand Down Expand Up @@ -653,7 +684,7 @@ describe('Response', function() {

it('should capture price when vars.cost is present', function() {
const vars =
{price_path: '/cost=([0-9]\.[0-9])/'};
{price_path: '/cost=([0-9].[0-9])/'};
const expected = {
outcome: 'success',
price: '1.5'
Expand Down Expand Up @@ -1099,11 +1130,6 @@ describe('Response', function() {
body
};

const expected = {
outcome: 'failure',
reason: 'the reason text!'
};

const event = response(vars, {}, res);
assert.equal(event.outcome, 'failure');
assert.equal(event.reason, 'the reason character data!');
Expand Down

0 comments on commit 0b64434

Please sign in to comment.