Returning Json Object as Response in Spring Boot

Spring boot how to return json embedded in object structure

You can use AbstractHttpMessageConverter for custom mapping operations for your problem.

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

@Component
public class CustomMessageConverter extends AbstractHttpMessageConverter<Map<String, Map<String, Object>>> {
public CustomMessageConverter() {
super(MediaType.APPLICATION_JSON);
}

@Override
protected boolean supports(Class<?> clazz) {
return HashMap.class.isAssignableFrom(clazz);
}

@Override
protected Map<String, Map<String, Object>> readInternal(Class<? extends Map<String, Map<String, Object>>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}

@Override
protected void writeInternal(Map<String, Map<String, Object>> value, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
PrintStream printStream = new PrintStream(outputMessage.getBody());

printStream.print("{");

// track to put comma between items
boolean printedEntry = false;

for (Map.Entry<String, Map<String, Object>> entry : value.entrySet()) {
/* // if you want to disable null print uncomment it
if (entry.getValue() == null) {
continue;
}
*/
if (printedEntry) {
printStream.print(",");
}

printStream.print("\"" + entry.getKey() + "\":");
if (entry.getValue() == null) {
printStream.print("null");
continue;
}

// INNER ENTRY LOOP
printStream.print("{");
// track to put comma between items
boolean printedInnerEntry = false;
for (Map.Entry<String, Object> innerEntry : entry.getValue().entrySet()) {
if (printedInnerEntry) {
printStream.print(",");
}
printStream.print("\"" + innerEntry.getKey() + "\":");
printStream.print(innerEntry.getValue());
printedInnerEntry = true;
}
printStream.print("}");

printedEntry = true;
}
printStream.print("}");
}
}
Test
@RestController
public class HomeController {
@GetMapping
public ResponseEntity<Map<String, Map<String, Object>>> getComplexStructure() {
Map<String, Map<String, Object>> result = new HashMap<>();

Map<String, Object> innerItem1 = new HashMap<>();
innerItem1.put("bar1", "{\"item\": 12, \"list\": [1, 2]}");
innerItem1.put("bar2", "{\"item\": 22, \"test\": \"test@\"}");

result.put("foo1", innerItem1);

Map<String, Object> innerItem2 = new HashMap<>();
innerItem2.put("tail1", "{\"item\": 55}");
innerItem2.put("tail2", "{\"item\": 77}");

result.put("foo2", innerItem2);

return ResponseEntity.ok(result);
}
}

GET http://localhost:8080/

Output

{"foo1":{"bar1":{"item": 12, "list": [1, 2]},"bar2":{"item": 22, "test": "test@"}},"foo2":{"tail1":{"item": 55},"tail2":{"item": 77}}}
Caution

I recommend that create a new class for operating this special case. Because HashMap is generic and caught all unrelated returns extended from HashMap. So, if you want to prefer this way, you can change below codes.

Create a new return type from HashMap<String, Map<String, Object>>

public class CustomMessage extends HashMap<String, Map<String, Object>> {
}

In CustomMessageConverter change supports method with below.

@Override
protected boolean supports(Class<?> clazz) {
return CustomMessage.class.isAssignableFrom(clazz);
}

Also, you can change AbstractHttpMessageConverter<CustomMessage> instead of AbstractHttpMessageConverter<Map<String, Map<String, Object>>> but it doesn't required.

Change return type with this new class.

public ResponseEntity<CustomMessage> getComplexStructure() {
CustomMessage result = new CustomMessage();
...
return ResponseEntity.ok(result);
}

why is Spring Boot returning a string instead of JSON

While you were right about single quotes, you can achieve the JSON response without using DTO if you don't want to. You can try this:

@PostMapping("/api/signup")
public ResponseEntity signup(HttpServletRequest request, HttpServletResponse response) {
return ResponseEntity
.status(<http_status>)
.contentType(MediaType.APPLICATION_JSON)
.body("{\"status\":\"fail\", \"message\":\"foo\"}");
}


Related Topics



Leave a reply



Submit