Reputation: 12731
My question is essentially a follow-up to this question.
@RestController
public class TestController
{
@RequestMapping("/getString")
public String getString()
{
return "Hello World";
}
}
In the above, Spring would add "Hello World" into the response body. How can I return a String as a JSON response? I understand that I could add quotes, but that feels more like a hack.
Please provide any examples to help explain this concept.
Note: I don't want this written straight to the HTTP Response body, I want to return the String in JSON format (I'm using my Controller with RestyGWT which requires the response to be in valid JSON format).
Upvotes: 184
Views: 541229
Reputation: 1842
One way to make it work for a single controller is to wrap it as a TextNode
:
@RestController
public class TestController {
private Map<String, Object> values;
@RequestMapping("/get/{id}")
public Object getValueAsValidJson(@PathVariable("id") final String id) {
final var value = values.get(id);
return value instanceof String string ?
TextNode.valueOf(string) :
value;
}
}
The TextNode
won't be handled by the StringHttpMessageConverter
, but will be passed through to the MappingJackson2HttpMessageConverter
.
Upvotes: 0
Reputation: 3895
Either return text/plain
(as in Return only string message from Spring MVC 3 Controller) OR wrap your String in some object
public class StringResponse {
private String response;
public StringResponse(String s) {
this.response = s;
}
// get/set omitted...
}
Set your response type to MediaType.APPLICATION_JSON_VALUE
(= "application/json"
)
@RequestMapping(value = "/getString", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
and you'll have a JSON that looks like
{ "response" : "your string value" }
Upvotes: 216
Reputation: 4208
Annotate your method with the @ResponseBody annotation to tell spring you are not trying to render a view and simple return the string plain
Upvotes: 0
Reputation: 1144
Simple and Straightforward send any object or return simple List
@GetMapping("/response2")
@ResponseStatus(HttpStatus.CONFLICT)
@ResponseBody List<String> Response2() {
List<String> response = new ArrayList<>(Arrays.asList("Response2"));
return response;
}
I have added HttpStatus.CONFLICT
as Random response to show how to pass RequestBody also the HttpStatus
Upvotes: 1
Reputation: 23947
Simply unregister the default StringHttpMessageConverter
instance:
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
/**
* Unregister the default {@link StringHttpMessageConverter} as we want Strings
* to be handled by the JSON converter.
*
* @param converters List of already configured converters
* @see WebMvcConfigurationSupport#addDefaultHttpMessageConverters(List)
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(c -> c instanceof StringHttpMessageConverter);
}
}
Tested with both controller action handler methods and controller exception handlers:
@RequestMapping("/foo")
public String produceFoo() {
return "foo";
}
@ExceptionHandler(FooApiException.class)
public String fooException(HttpServletRequest request, Throwable e) {
return e.getMessage();
}
Final notes:
extendMessageConverters
is available since Spring 4.1.3, if are running on a previous version you can implement the same technique using configureMessageConverters
, it just takes a little bit more work.Upvotes: 28
Reputation: 5176
This issue has driven me mad: Spring is such a potent tool and yet, such a simple thing as writing an output String as JSON seems impossible without ugly hacks.
My solution (in Kotlin) that I find the least intrusive and most transparent is to use a controller advice and check whether the request went to a particular set of endpoints (REST API typically since we most often want to return ALL answers from here as JSON and not make specializations in the frontend based on whether the returned data is a plain string ("Don't do JSON deserialization!") or something else ("Do JSON deserialization!")). The positive aspect of this is that the controller remains the same and without hacks.
The supports
method makes sure that all requests that were handled by the StringHttpMessageConverter
(e.g. the converter that handles the output of all controllers that return plain strings) are processed and in the beforeBodyWrite
method, we control in which cases we want to interrupt and convert the output to JSON (and modify headers accordingly).
@ControllerAdvice
class StringToJsonAdvice(val ob: ObjectMapper) : ResponseBodyAdvice<Any?> {
override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean =
converterType === StringHttpMessageConverter::class.java
override fun beforeBodyWrite(
body: Any?,
returnType: MethodParameter,
selectedContentType: MediaType,
selectedConverterType: Class<out HttpMessageConverter<*>>,
request: ServerHttpRequest,
response: ServerHttpResponse
): Any? {
return if (request.uri.path.contains("api")) {
response.getHeaders().contentType = MediaType.APPLICATION_JSON
ob.writeValueAsString(body)
} else body
}
}
I hope in the future that we will get a simple annotation in which we can override which HttpMessageConverter
should be used for the output.
Upvotes: 1
Reputation: 903
I know that this question is old but i would like to contribute too:
The main difference between others responses is the hashmap return.
@GetMapping("...")
@ResponseBody
public Map<String, Object> endPointExample(...) {
Map<String, Object> rtn = new LinkedHashMap<>();
rtn.put("pic", image);
rtn.put("potato", "King Potato");
return rtn;
}
This will return:
{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}
Upvotes: 27
Reputation: 12731
In one project we addressed this using JSONObject (maven dependency info). We chose this because we preferred returning a simple String rather than a wrapper object. An internal helper class could easily be used instead if you don't want to add a new dependency.
Example Usage:
@RestController
public class TestController
{
@RequestMapping("/getString")
public String getString()
{
return JSONObject.quote("Hello World");
}
}
Upvotes: 32
Reputation: 332
Make simple:
@GetMapping("/health")
public ResponseEntity<String> healthCheck() {
LOG.info("REST request health check");
return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
}
Upvotes: 10
Reputation: 2983
You can easily return JSON
with String
in property response
as following
@RestController
public class TestController {
@RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
public Map getString() {
return Collections.singletonMap("response", "Hello World");
}
}
Upvotes: 31
Reputation: 45
Add @ResponseBody
annotation, which will write return data in output stream.
Upvotes: 2
Reputation: 79
Add produces = "application/json"
in @RequestMapping
annotation like:
@RequestMapping(value = "api/login", method = RequestMethod.GET, produces = "application/json")
Hint: As a return value, i recommend to use ResponseEntity<List<T>>
type. Because the produced data in JSON body need to be an array or an object according to its specifications, rather than a single simple string. It may causes problems sometimes (e.g. Observables in Angular2).
Difference:
returned String
as json: "example"
returned List<String>
as json: ["example"]
Upvotes: 7
Reputation: 10300
JSON is essentially a String in PHP or JAVA context. That means string which is valid JSON can be returned in response. Following should work.
@RequestMapping(value="/user/addUser", method=RequestMethod.POST)
@ResponseBody
public String addUser(@ModelAttribute("user") User user) {
if (user != null) {
logger.info("Inside addIssuer, adding: " + user.toString());
} else {
logger.info("Inside addIssuer...");
}
users.put(user.getUsername(), user);
return "{\"success\":1}";
}
This is okay for simple string response. But for complex JSON response you should use wrapper class as described by Shaun.
Upvotes: 70