Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 6c8464a

Browse files
feat($http): support custom params serializers
Closes #3740 Closes #7429 Closes #9224 Closes #11461
1 parent 731c8b5 commit 6c8464a

File tree

3 files changed

+160
-26
lines changed

3 files changed

+160
-26
lines changed

src/AngularPublic.js

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
$IntervalProvider,
6868
$$HashMapProvider,
6969
$HttpProvider,
70+
$HttpParamSerializerProvider,
71+
$HttpParamSerializerJQLikeProvider,
7072
$HttpBackendProvider,
7173
$LocationProvider,
7274
$LogProvider,
@@ -224,6 +226,8 @@ function publishExternalAPI(angular) {
224226
$interpolate: $InterpolateProvider,
225227
$interval: $IntervalProvider,
226228
$http: $HttpProvider,
229+
$httpParamSerializer: $HttpParamSerializerProvider,
230+
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
227231
$httpBackend: $HttpBackendProvider,
228232
$location: $LocationProvider,
229233
$log: $LogProvider,

src/ng/http.js

+84-25
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,64 @@ var JSON_ENDS = {
99
};
1010
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
1111

12+
function paramSerializerFactory(jQueryMode) {
13+
14+
function serializeValue(v) {
15+
if (isObject(v)) {
16+
return isDate(v) ? v.toISOString() : toJson(v);
17+
}
18+
return v;
19+
}
20+
21+
return function paramSerializer(params) {
22+
if (!params) return '';
23+
var parts = [];
24+
forEachSorted(params, function(value, key) {
25+
if (value === null || isUndefined(value)) return;
26+
if (isArray(value) || isObject(value) && jQueryMode) {
27+
forEach(value, function(v, k) {
28+
var keySuffix = jQueryMode ? '[' + (!isArray(value) ? k : '') + ']' : '';
29+
parts.push(encodeUriQuery(key + keySuffix) + '=' + encodeUriQuery(serializeValue(v)));
30+
});
31+
} else {
32+
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
33+
}
34+
});
35+
36+
return parts.length > 0 ? parts.join('&') : '';
37+
};
38+
}
39+
40+
function $HttpParamSerializerProvider() {
41+
/**
42+
* @ngdoc service
43+
* @name $httpParamSerializer
44+
* @description
45+
*
46+
* Default $http params serializer that converts objects to a part of a request URL
47+
* according to the following rules:
48+
* * `{'foo': 'bar'}` results in `foo=bar`
49+
* * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
50+
* * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
51+
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
52+
* */
53+
this.$get = function() {
54+
return paramSerializerFactory(false);
55+
};
56+
}
57+
58+
function $HttpParamSerializerJQLikeProvider() {
59+
/**
60+
* @ngdoc service
61+
* @name $httpParamSerializerJQLike
62+
*
63+
* Alternative $http params serializer that follows jQuerys `param()` method {http://api.jquery.com/jquery.param/} logic.
64+
* */
65+
this.$get = function() {
66+
return paramSerializerFactory(true);
67+
};
68+
}
69+
1270
function defaultHttpResponseTransform(data, headers) {
1371
if (isString(data)) {
1472
// Strip json vulnerability protection prefix and trim whitespace
@@ -153,6 +211,11 @@ function $HttpProvider() {
153211
* - **`defaults.headers.put`**
154212
* - **`defaults.headers.patch`**
155213
*
214+
* - **`defaults.paramSerializer`** - {string|function(Object<string,string>):string} - A function used to prepare string representation
215+
* of request parameters (specified as an object).
216+
* Is specified as string, it is interpreted as function registered in with the {$injector}.
217+
* Defaults to {$httpParamSerializer}.
218+
*
156219
**/
157220
var defaults = this.defaults = {
158221
// transform incoming response data
@@ -174,7 +237,9 @@ function $HttpProvider() {
174237
},
175238

176239
xsrfCookieName: 'XSRF-TOKEN',
177-
xsrfHeaderName: 'X-XSRF-TOKEN'
240+
xsrfHeaderName: 'X-XSRF-TOKEN',
241+
242+
paramSerializer: '$httpParamSerializer'
178243
};
179244

180245
var useApplyAsync = false;
@@ -188,7 +253,7 @@ function $HttpProvider() {
188253
* significant performance improvement for bigger applications that make many HTTP requests
189254
* concurrently (common during application bootstrap).
190255
*
191-
* Defaults to false. If no value is specifed, returns the current configured value.
256+
* Defaults to false. If no value is specified, returns the current configured value.
192257
*
193258
* @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
194259
* "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
@@ -225,6 +290,12 @@ function $HttpProvider() {
225290

226291
var defaultCache = $cacheFactory('$http');
227292

293+
/**
294+
* Make sure that default param serializer is exposed as a function
295+
*/
296+
defaults.paramSerializer = isString(defaults.paramSerializer) ?
297+
$injector.get(defaults.paramSerializer) : defaults.paramSerializer;
298+
228299
/**
229300
* Interceptors stored in reverse order. Inner interceptors before outer interceptors.
230301
* The reversal is needed so that we can build up the interception chain around the
@@ -636,6 +707,9 @@ function $HttpProvider() {
636707
* response body, headers and status and returns its transformed (typically deserialized) version.
637708
* See {@link ng.$http#overriding-the-default-transformations-per-request
638709
* Overriding the Default Transformations}
710+
* - **paramSerializer** - {string|function(Object<string,string>):string} - A function used to prepare string representation
711+
* of request parameters (specified as an object).
712+
* Is specified as string, it is interpreted as function registered in with the {$injector}.
639713
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
640714
* GET request, otherwise if a cache instance built with
641715
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
@@ -764,11 +838,14 @@ function $HttpProvider() {
764838
var config = extend({
765839
method: 'get',
766840
transformRequest: defaults.transformRequest,
767-
transformResponse: defaults.transformResponse
841+
transformResponse: defaults.transformResponse,
842+
paramSerializer: defaults.paramSerializer
768843
}, requestConfig);
769844

770845
config.headers = mergeHeaders(requestConfig);
771846
config.method = uppercase(config.method);
847+
config.paramSerializer = isString(config.paramSerializer) ?
848+
$injector.get(config.paramSerializer) : config.paramSerializer;
772849

773850
var serverRequest = function(config) {
774851
var headers = config.headers;
@@ -1032,7 +1109,7 @@ function $HttpProvider() {
10321109
cache,
10331110
cachedResp,
10341111
reqHeaders = config.headers,
1035-
url = buildUrl(config.url, config.params);
1112+
url = buildUrl(config.url, config.paramSerializer(config.params));
10361113

10371114
$http.pendingRequests.push(config);
10381115
promise.then(removePendingReq, removePendingReq);
@@ -1139,27 +1216,9 @@ function $HttpProvider() {
11391216
}
11401217

11411218

1142-
function buildUrl(url, params) {
1143-
if (!params) return url;
1144-
var parts = [];
1145-
forEachSorted(params, function(value, key) {
1146-
if (value === null || isUndefined(value)) return;
1147-
if (!isArray(value)) value = [value];
1148-
1149-
forEach(value, function(v) {
1150-
if (isObject(v)) {
1151-
if (isDate(v)) {
1152-
v = v.toISOString();
1153-
} else {
1154-
v = toJson(v);
1155-
}
1156-
}
1157-
parts.push(encodeUriQuery(key) + '=' +
1158-
encodeUriQuery(v));
1159-
});
1160-
});
1161-
if (parts.length > 0) {
1162-
url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
1219+
function buildUrl(url, serializedParams) {
1220+
if (serializedParams.length > 0) {
1221+
url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
11631222
}
11641223
return url;
11651224
}

test/ng/httpSpec.js

+72-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
describe('$http', function() {
44

55
var callback, mockedCookies;
6+
var customParamSerializer = function(params) {
7+
return Object.keys(params).join('_');
8+
};
69

710
beforeEach(function() {
811
callback = jasmine.createSpy('done');
@@ -14,6 +17,9 @@ describe('$http', function() {
1417
});
1518
});
1619

20+
beforeEach(module({
21+
customParamSerializer: customParamSerializer
22+
}));
1723
beforeEach(module(function($exceptionHandlerProvider) {
1824
$exceptionHandlerProvider.mode('log');
1925
}));
@@ -354,6 +360,20 @@ describe('$http', function() {
354360
$httpBackend.expect('GET', '/url?date=2014-07-15T17:30:00.000Z').respond('');
355361
$http({url: '/url', params: {date:new Date('2014-07-15T17:30:00.000Z')}, method: 'GET'});
356362
});
363+
364+
365+
describe('custom params serialization', function() {
366+
367+
it('should allow specifying custom paramSerializer as function', function() {
368+
$httpBackend.expect('GET', '/url?foo_bar').respond('');
369+
$http({url: '/url', params: {foo: 'fooVal', bar: 'barVal'}, paramSerializer: customParamSerializer});
370+
});
371+
372+
it('should allow specifying custom paramSerializer as function from DI', function() {
373+
$httpBackend.expect('GET', '/url?foo_bar').respond('');
374+
$http({url: '/url', params: {foo: 'fooVal', bar: 'barVal'}, paramSerializer: 'customParamSerializer'});
375+
});
376+
});
357377
});
358378

359379

@@ -1788,11 +1808,16 @@ describe('$http', function() {
17881808
$httpBackend.flush();
17891809
});
17901810

1791-
it('should have separate opbjects for defaults PUT and POST', function() {
1811+
it('should have separate objects for defaults PUT and POST', function() {
17921812
expect($http.defaults.headers.post).not.toBe($http.defaults.headers.put);
17931813
expect($http.defaults.headers.post).not.toBe($http.defaults.headers.patch);
17941814
expect($http.defaults.headers.put).not.toBe($http.defaults.headers.patch);
17951815
});
1816+
1817+
it('should expose default param serializer at runtime', function() {
1818+
var paramSerializer = $http.defaults.paramSerializer;
1819+
expect(paramSerializer({foo: 'foo', bar: ['bar', 'baz']})).toEqual('bar=bar&bar=baz&foo=foo');
1820+
});
17961821
});
17971822
});
17981823

@@ -1929,3 +1954,49 @@ describe('$http with $applyAsync', function() {
19291954
expect(log).toEqual(['response 1', 'response 2', 'response 3']);
19301955
});
19311956
});
1957+
1958+
describe('$http param serializers', function() {
1959+
1960+
var defSer, jqrSer;
1961+
beforeEach(inject(function($httpParamSerializer, $httpParamSerializerJQLike) {
1962+
defSer = $httpParamSerializer;
1963+
jqrSer = $httpParamSerializerJQLike;
1964+
}));
1965+
1966+
describe('common functionality', function() {
1967+
1968+
it('should return empty string for null or undefined params', function() {
1969+
expect(defSer(undefined)).toEqual('');
1970+
expect(jqrSer(undefined)).toEqual('');
1971+
expect(defSer(null)).toEqual('');
1972+
expect(jqrSer(null)).toEqual('');
1973+
});
1974+
1975+
it('should serialize objects', function() {
1976+
expect(defSer({foo: 'foov', bar: 'barv'})).toEqual('bar=barv&foo=foov');
1977+
expect(jqrSer({foo: 'foov', bar: 'barv'})).toEqual('bar=barv&foo=foov');
1978+
});
1979+
1980+
});
1981+
1982+
describe('default array serialization', function() {
1983+
1984+
it('should serialize arrays by repeating param name', function() {
1985+
expect(defSer({a: 'b', foo: ['bar', 'baz']})).toEqual('a=b&foo=bar&foo=baz');
1986+
});
1987+
});
1988+
1989+
describe('jquery array and objects serialization', function() {
1990+
1991+
it('should serialize arrays by repeating param name with [] suffix', function() {
1992+
expect(jqrSer({a: 'b', foo: ['bar', 'baz']})).toEqual('a=b&foo%5B%5D=bar&foo%5B%5D=baz');
1993+
expect(decodeURIComponent(jqrSer({a: 'b', foo: ['bar', 'baz']}))).toEqual('a=b&foo[]=bar&foo[]=baz');
1994+
});
1995+
1996+
it('should serialize objects by repeating param name with [kay] suffix', function() {
1997+
expect(jqrSer({a: 'b', foo: {'bar': 'barv', 'baz': 'bazv'}})).toEqual('a=b&foo%5Bbar%5D=barv&foo%5Bbaz%5D=bazv');
1998+
//a=b&foo[bar]=barv&foo[baz]=bazv
1999+
});
2000+
});
2001+
2002+
});

0 commit comments

Comments
 (0)