Why serializeBody method in request.ts does not convert JSON.stringify(this.body) in case string?
Asked Answered
S

2

0

When i pass string value into post method, this value was not converted JSON. So We could not get this value in Controller.

I checked following code and find the reason why value was not converted.

Link

But I could not understand this behavior is correct.

apply(id: string): Observable<boolean> {
    return this.http.post<boolean>(
      REQUEST_URL + '/applicationrequest',
      id,
      HEADERS
    );
  }
@ResponseBody
    @RequestMapping(value = "/applicationrequest", method = RequestMethod.POST)
    public boolean apply(@RequestBody UUID id) {
        return usecase.apply(id);
    }

I want to know the reason why string should not be converted JSON automatically. And if way to use httpClient, I want to know the correct way to use. Thank you.

Angular Version is 8.1.0. Java is Java 8. Spring is 5.1 / Spring Boot 2.1

Stomachic answered 2/8, 2019 at 9:44 Comment(0)
C
0

Simply because those are 2 totally different things. Angular (or the httpclient) can't know by magic what exactly the endpoint requires or expects as input.

It's simply up to you as a developer to generate the http request that matches the server's interface.

Cabbagehead answered 2/8, 2019 at 9:58 Comment(2)
Thank you for the response. but so why boolean value always convert into JSON?Stomachic
I don't agree with you, because when the Content-Type is set to application/json (or text/json) it's clear that the body should be serialized as a JSON string.Gurdwara
G
4

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.

Gurdwara answered 15/2, 2021 at 14:0 Comment(0)
C
0

Simply because those are 2 totally different things. Angular (or the httpclient) can't know by magic what exactly the endpoint requires or expects as input.

It's simply up to you as a developer to generate the http request that matches the server's interface.

Cabbagehead answered 2/8, 2019 at 9:58 Comment(2)
Thank you for the response. but so why boolean value always convert into JSON?Stomachic
I don't agree with you, because when the Content-Type is set to application/json (or text/json) it's clear that the body should be serialized as a JSON string.Gurdwara

© 2022 - 2024 — McMap. All rights reserved.