Ka Tech
Ka Tech

Reputation: 9457

Angular Http Client - How to Pass Nested Params Object to GET API

I am using latest version of angular (8). I am doing a conversion of my Http request from the original http to the new http client. I am calling a GET API where I am sending a nested params in the following way:

let data: any = {filters: {"4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"}, is_indexed: true}
return this.httpClient.get<Register[]>('/registers/', {headers: headers, params: data});

The above code, the nested params are not recognised. How do I properly pass as nested params using the HttpClient? Ps In the old Http, there was no issue using the above code. Thanks for the help in advance.

UPDATE Url would look similar to this:

https://www.somepapi.com/registers/?filters=%7B%224e9bc554-db54-4413-a718-b89ffdb91c2f%22:%228465c1ab-2b89-4b51-8a7b-5d2ac862ee32%22%7D&is_indexed=true;

Upvotes: 6

Views: 4579

Answers (5)

Bigmwaj
Bigmwaj

Reputation: 391

After many searching, I think that there is no way to pass nested json object to get api. Nethertheless, I have a solution for when your webservice is build with spring boot. Here is what you can do.

At the angular side I sugest you to encode your json query to base64 to shorten it:

let data: any = {filters: {"4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"}, is_indexed: true}

let objJsonStr = JSON.stringify(data);
let objJsonB64 = btoa(objJsonStr);
let queryParams = new HttpParams();
queryParams = queryParams.append('q', objJsonB64);

return this.httpClient.get<Register[]>('/registers/', {headers: headers, params: queryParams});

At the spring boot side: Create a servletRequest mapper that will help to transform you query and inject in the body

package com.example.ws.servlet;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class SeachQueryHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private static final String CHARSET = "UTF-8";
    
    private static final String CONTENT_TYPE = "application/json";
    
    private boolean loaded;

    public SeachQueryHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (loaded) {
            throw new RuntimeException("Already loaded!");
        }

        var query = getRequest().getParameter("q");
        var decodedBytes = Base64.getDecoder().decode(query);
        var decodedString = new String(decodedBytes);
        
        try (var body = new BodyInputStream(decodedString.getBytes(getCharacterEncoding()))) {
            loaded = true;
            return body;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
    
    @Override
    public String getCharacterEncoding() {
        return CHARSET;
    }
    
    @Override
    public String getContentType() {
        return CONTENT_TYPE;
    }

    /**
     * The exact copy of 
     * @see org.springframework.web.servlet.function.DefaultServerRequestBuilder$BodyInputStream
     */
    private static class BodyInputStream extends ServletInputStream {

        private final InputStream delegate;

        public BodyInputStream(byte[] body) {
            this.delegate = new ByteArrayInputStream(body);
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int read() throws IOException {
            return this.delegate.read();
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            return this.delegate.read(b, off, len);
        }

        @Override
        public int read(byte[] b) throws IOException {
            return this.delegate.read(b);
        }

        @Override
        public long skip(long n) throws IOException {
            return this.delegate.skip(n);
        }

        @Override
        public int available() throws IOException {
            return this.delegate.available();
        }

        @Override
        public void close() throws IOException {
            this.delegate.close();
        }

        @Override
        public synchronized void mark(int readlimit) {
            this.delegate.mark(readlimit);
        }

        @Override
        public synchronized void reset() throws IOException {
            this.delegate.reset();
        }

        @Override
        public boolean markSupported() {
            return this.delegate.markSupported();
        }
    }
}

Add a filter to transform your request

package com.example.ws.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.context.annotation.Configuration;

import com.example.ws.servlet.SeachQueryHttpServletRequestWrapper;

@Configuration
public class SeachQueryFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, 
            ServletResponse response, FilterChain filterchain)
            throws IOException, ServletException {
                
        var query = request.getParameter("q");

        if( query != null ) {
            var wrapper = new SeachQueryHttpServletRequestWrapper((HttpServletRequest) request);
            filterchain.doFilter(wrapper, response);
        }else {
            filterchain.doFilter(request, response);
        }
    }
}

And voilà!

Upvotes: 1

Franck MARIN
Franck MARIN

Reputation: 71

You can use qs to stringify your object to url query string and then use the native fromString import on HttpParams constructor

let data: any = {filters: {"4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"}, is_indexed: true}

const params = new HttpParams({fromString: qs.stringify(data)});

return this.httpClient.get<Register[]>('/registers/', {headers: headers, params});

Work fine for me with complex object.

See https://npmjs.com/package/qs and https://www.npmjs.com/package/@types/qs

Upvotes: 1

jbobbins
jbobbins

Reputation: 1291

Here's my working method of passing nested objects in Angular by serializing with JSON.stringify:

const stringifiedParams = JSON.stringify(this.filterParams);
return this.http.get<any[]>(`jobs`, {params: {filter: stringifiedParams}});

In Node this deserializes the params with JSON.parse:

filter = JSON.parse(req.query.filter.toString());

Upvotes: 1

Mathias
Mathias

Reputation: 1980

You could recursively iterate through the data object to flatten it out.

getData(data: any) {
  let params = {};    

  this.buildHttpParams(params, data, "");

  return this.httpClient.get("...", {params: params})
}

private buildHttpParams(params: any, data: any, currentPath: string) {
    Object.keys(data).forEach(key => {
        if (data[key] instanceof Object) {
            this.buildHttpParams(params, data[key], `${currentPath}${key}.`);
        } else {
            params[`${currentPath}${key}`] = data[key];
        }
    });
}

This will change our data object from:

{
  filters: {
    "4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"
  },
  "is_indexed": true
}

to:

{
  "filters.4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32",
  "is_indexed": true
}

Upvotes: 4

Immad Hamid
Immad Hamid

Reputation: 789

Your solution should be implemented like this now:

import { HttpClient,HttpParams } from '@angular/common/http';

let params: any = new HttpParams();

// Begin assigning parameters
params = params.append('filters', {"4e9bc554-db54": "23473473-7938", "555555-db54": "33333-7938"});
params = params.append('is_indexed', true);
return this.httpClient.get<Register[]>('/registers/', {headers: headers, { params });

Other than this there's a set property too, you can check further in the Angular's Documentation.

Upvotes: -1

Related Questions