The question is somewhat different from Spring's RestTemplate: complex object to query params Suppose there is a request that takes some parameters in the form of an object:
@GetMapping("getRoute")
public ActionResult<Response> get(@Validated GetRequest req) {
//do some logic
}
The request itself looks like this:
public class GetRequest{
private String firstField;
private Long secondField;
private ComplexObject thirdField;
private static class ComplexObject{
private String subFirstField;
private Long subSecondField;
}
}
Therefore, when I execute a query with this object from RestTemplate, I want to get a URI like this:
/getRoute?firstField=val&secondField=val&thirdField.subFirstField=val&thirdField.subSecondField=val
How can i do this? The object can be absolutely anything. The bigger question is how to translate such an this object into a MultiValueMap for UriComponentsBuilder.
The solution would be simple if it were a POST request, but I need it for GET.
I only know that the Springfox library uses this approach when it generates the Swagger API, but the logic inside is too complex. My scenario:
@SuppressWarnings("unchecked")
public <T> ActionResult<T> getAction(String relativeUrl, Class<T> responseType, @Nullable Object paramsObject) {
UriComponentsBuilder builder = createUriComponentsBuilder(relativeUrl, paramsObject);
final ApiErrorCode errorCode;
try {
ResponseEntity<ActionResult> responseEntity = this.restTemplate.getForEntity(builder.build().toUri(), ActionResult.class);
ActionResult responseBody = responseEntity.getBody();
if (!Objects.requireNonNull(responseBody).isSuccess()) {
return responseBody;
}
return ActionResult.ok(this.mapper.convertValue(responseBody.getValue(), responseType));
} catch (RestClientException | HttpMessageNotReadableException e) {
errorCode = ApiErrorCode.API_CONNECTION_ERROR;
} catch (Exception e) {
errorCode = ApiErrorCode.INTERNAL_SERVER_ERROR;
}
return ActionResult.fail(errorCode);
}
private UriComponentsBuilder createUriComponentsBuilder(String relativeUrl, @Nullable Object object) {
String url = this.baseUrl;
if (StringUtils.hasText(relativeUrl)) {
url += relativeUrl;
}
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
if (object != null) {
builder.queryParams(this.convertToMultiValueMap(object));
}
return builder;
}
private MultiValueMap<String, String> convertToMultiValueMap(Object object) {
//todo object to params
}
I do not know why I got a downvote. But I tried to make my implementation, perhaps somewhere there may be unforeseen errors.
public static LinkedMultiValueMap<String, String> toUrlParams(Object value) {
final LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
final List<Field> declaredFields = getAllFields(value);
for (Field field : declaredFields) {
ReflectionUtils.makeAccessible(field);
final String name = field.getName();
final Object fieldVal = ReflectionUtils.getField(field, value);
mapFields(params, name, fieldVal);
}
return params;
}
private static List<Field> getAllFields(Object t) {
final List<Field> fields = new ArrayList<>();
Class clazz = t.getClass();
while (clazz.getSuperclass() != null) {
final Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
final int modifiers = field.getModifiers();
if (!(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers))) {
fields.add(field);
}
}
clazz = clazz.getSuperclass();
}
return fields;
}
private static void mapFields(LinkedMultiValueMap<String, String> params,
String fieldName,
@Nullable Object fieldVal) {
if (fieldVal != null) {
final Class<?> fieldClass = fieldVal.getClass();
if (BeanUtils.isSimpleValueType(fieldClass) || fieldVal instanceof Number || fieldVal instanceof UUID) {
if (fieldClass.isEnum()) {
params.add(fieldName, ((Enum) fieldVal).name());
} else {
params.add(fieldName, fieldVal.toString());
}
} else {
if (fieldVal instanceof Map) {
throw new IllegalArgumentException("Map is not allowed for url params");
}
if (fieldVal instanceof List) {
final Iterator iterator = ((Iterable) fieldVal).iterator();
int i = 0;
while (iterator.hasNext()) {
final Object iterElement = iterator.next();
mapFields(params, fieldName + "[" + i + "]", iterElement);
i++;
}
} else {
if (fieldVal instanceof Set) {
for (Object iterElement : ((Iterable) fieldVal)) {
mapFields(params, fieldName, iterElement);
}
} else {
if (fieldVal instanceof Collection) {
throw new IllegalArgumentException("Unknown collection, expected List or Set, but was " + fieldVal.getClass());
}
if (fieldVal.getClass().isArray()) {
final int length = Array.getLength(fieldVal);
for (int i = 0; i < length; i++) {
Object arrayElement = Array.get(fieldVal, i);
mapFields(params, fieldName + "[" + i + "]", arrayElement);
}
} else {
final List<Field> declaredFields = getAllFields(fieldVal);
for (Field field : declaredFields) {
ReflectionUtils.makeAccessible(field);
final String name = field.getName();
final Object nestedField = ReflectionUtils.getField(field, fieldVal);
mapFields(params, fieldName + "." + name, nestedField);
}
}
}
}
}
}
}