diff --git a/src/interceptors/multipart_request.js b/src/interceptors/multipart_request.js index 7c4a0b1..7ac4b2d 100644 --- a/src/interceptors/multipart_request.js +++ b/src/interceptors/multipart_request.js @@ -1,4 +1,5 @@ import _ from 'lodash'; +import { entries } from '../utils'; /** Takes `params` from `ctx` and converts to `FormData` @@ -17,14 +18,11 @@ export default class MultipartRequest { ); } - const formDataObj = _.reduce( - params, - (fd, val, key) => { - fd.append(key, val); - return fd; - }, - new FormData() - ); + const formDataObj = entries(v).reduce((fd, entry) => { + const [val, key] = entry; + fd.append(key, val); + return fd; + }, new FormData()); /* eslint-enable no-undef */ return { params: formDataObj, ...ctx }; diff --git a/src/serializer.js b/src/serializer.js index 9fc5c16..8a6fc2c 100644 --- a/src/serializer.js +++ b/src/serializer.js @@ -3,6 +3,7 @@ import transit from 'transit-js'; import _ from 'lodash'; import { UUID, LatLng, Money, BigDecimal, toType } from './types'; +import { entries } from './utils'; /** Composes two readers (sdk type and app type) so that: @@ -168,14 +169,11 @@ const MapHandler = [ transit.makeWriteHandler({ tag: () => 'map', rep: v => - _.reduce( - v, - (map, val, key) => { - map.set(transit.keyword(key), val); - return map; - }, - transit.map() - ), + entries(v).reduce((map, entry) => { + const [key, val] = entry; + map.set(transit.keyword(key), val); + return map; + }, transit.map()), }), ]; diff --git a/src/serializer.test.js b/src/serializer.test.js index e95f7bc..b292fcf 100644 --- a/src/serializer.test.js +++ b/src/serializer.test.js @@ -19,6 +19,24 @@ describe('serializer', () => { expect(r.read(w.write(testData))).toEqual(testData); }); + it('reads and writes Object with key length', () => { + // See: https://github.com/lodash/lodash/issues/5870 + const testData = { + a: 1, + b: 2, + c: [3, 4, 5], + d: { + e: true, + }, + length: 10, + }; + + const r = reader(); + const w = writer(); + + expect(r.read(w.write(testData))).toEqual(testData); + }); + it('reads and writes transit JSON verbose', () => { const testData = { a: 1, diff --git a/src/utils.js b/src/utils.js index c3646d2..99bfa79 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,15 @@ import _ from 'lodash'; +/** + Null-safe version of Object.entries + */ +export const entries = obj => { + if (obj == null) { + return []; + } + return Object.entries(obj); +}; + /** Take URL and remove the trailing slashes. @@ -17,14 +27,13 @@ export const fnPath = path => _.without(path.split('/'), '').map(part => part.replace(/_\w/g, m => m[1].toUpperCase())); export const formData = params => - _.reduce( - params, - (pairs, v, k) => { + entries(params) + .reduce((pairs, entry) => { + const [k, v] = entry; pairs.push(`${encodeURIComponent(k)}=${encodeURIComponent(v)}`); return pairs; - }, - [] - ).join('&'); + }, []) + .join('&'); /** Serialize a single attribute in an object query parameter. diff --git a/src/utils.test.js b/src/utils.test.js index a9f896a..cc04406 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -1,4 +1,10 @@ -import { fnPath, trimEndSlash, formData, objectQueryString, canonicalAssetPaths } from './utils'; +import { + fnPath, + trimEndSlash, + formData, + objectQueryString, + canonicalAssetPaths, +} from './utils'; describe('utils', () => { describe('pathToMethodName', () => { @@ -33,6 +39,11 @@ describe('utils', () => { formData({ username: 'joe.dunphy@example.com', password: '}4$3.872487=3&&]/6?.' }) ).toEqual('username=joe.dunphy%40example.com&password=%7D4%243.872487%3D3%26%26%5D%2F6%3F.'); }); + + it('encodes Object with key length', () => { + // See: https://github.com/lodash/lodash/issues/5870 + expect(formData({ length: 10 })).toEqual('length=10'); + }); }); describe('objectQueryString', () => {