Someone in my team also discovered this issue and we needed to add JSON.stringify(...)
for all HTTP PUT/POST methods that only accept a string in the body. Our generated proxies (we use Swagger) didn't add this for us.
The actual problem is here in the Angular code:
/**
* Transform the free-form body into a serialized format suitable for
* transmission to the server.
*/
serializeBody(): ArrayBuffer | Blob | FormData | string | null {
// If no body is present, no need to serialize it.
if (this.body === null) {
return null;
}
// Check whether the body is already in a serialized form. If so,
// it can just be returned directly.
if (isArrayBuffer(this.body) || isBlob(this.body) || isFormData(this.body) ||
typeof this.body === 'string') {
return this.body;
}
// Check whether the body is an instance of HttpUrlEncodedParams.
if (this.body instanceof HttpParams) {
return this.body.toString();
}
// Check whether the body is an object or array, and serialize with JSON if so.
if (typeof this.body === 'object' || typeof this.body === 'boolean' ||
Array.isArray(this.body)) {
return JSON.stringify(this.body);
}
// Fall back on toString() for everything else.
return (this.body as any).toString();
}
A normal string will use the last option and will be rendered as a plain string, instead of going through JSON.stringify
.
I created an HTTP interceptor that checks the content-type and if it's set to application/json
or text/json
and the body is a plain string, then the interceptor calls JSON.stringify
to make sure it's a valid JSON string. This interceptor looks like this:
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class StringInterceptor implements HttpInterceptor{
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (typeof request.body === 'string') {
const contentType = request.headers.get('Content-Type')
if (contentType == 'application/json' || contentType == 'text/json') {
request = request.clone({
body: JSON.stringify(request.body)
});
}
}
return next.handle(request)
}
}
You also need to register the interceptor by adding the following code in app.module.ts
(make sure you import your interceptor) to the provider registration:
{
provide: HTTP_INTERCEPTORS,
useClass: StringInterceptor,
multi: true
},
Also make sure you remove your own JSON.stringify(...)
calls to avoid that all your strings will have excessive quotes.
boolean
value always convert intoJSON
? – Stomachic