Spring MVC: cómo devolver una cadena simple como JSON en Rest Controller

137

Mi pregunta es esencialmente un seguimiento de esta pregunta.

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return "Hello World";
    }
}

En lo anterior, Spring agregaría "Hello World" en el cuerpo de respuesta. ¿Cómo puedo devolver una cadena como respuesta JSON? Entiendo que podría agregar citas, pero eso se siente más como un truco.

Proporcione algunos ejemplos para ayudar a explicar este concepto.

Nota: No quiero que esto se escriba directamente en el cuerpo de Respuesta HTTP, quiero devolver la Cadena en formato JSON (estoy usando mi Controlador con RestyGWT que requiere que la respuesta esté en formato JSON válido).

La daga de Gilbert Arenas
fuente
Puede devolver Map o cualquier objeto / entidad que contenga su cadena
Denys Denysiuk
¿Entonces quiere decir que desea que el valor de la cadena se serialice en una cadena JSON?
Sotirios Delimanolis 17/06/2015

Respuestas:

150

Cualquiera de retorno text/plain(como en el de vuelta Solamente el mensaje de cadena a partir de la primavera del controlador MVC 3 ) o una envoltura de la cadena es un objeto

public class StringResponse {

    private String response;

    public StringResponse(String s) { 
       this.response = s;
    }

    // get/set omitted...
}


Establezca su tipo de respuesta en MediaType.APPLICATION_JSON_VALUE(= "application/json")

@RequestMapping(value = "/getString", method = RequestMethod.GET,
                produces = MediaType.APPLICATION_JSON_VALUE)

y tendrás un JSON que se parece a

{  "response" : "your string value" }
Shaun
fuente
124
También puede volver Collections.singletonMap("response", "your string value")para lograr el mismo resultado sin tener que crear una clase de contenedor.
Bohuslav Burghardt
@Bohuslav Ese es un gran consejo.
Shaun
66
No es cierto que requiera una clave y un valor. Una sola cadena o un conjunto de cadenas son JSON válidos. Si no está de acuerdo, tal vez pueda explicar por qué el sitio web jsonlint acepta ambos como JSON válidos.
KyleM
2
¿Cómo se convierte la clase contenedora en un JSON?
Rocky Inde
3
Creo que es suficiente para regresarCollections.singleton("your string value")
gauee
54

JSON es esencialmente una cadena en contexto PHP o JAVA. Eso significa que la cadena que es JSON válida se puede devolver en respuesta. Lo siguiente debería funcionar.

  @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}";
  }

Esto está bien para una respuesta de cadena simple. Pero para una respuesta JSON compleja, debe usar la clase de contenedor como lo describe Shaun.

vansia rosada
fuente
77
Esta debe ser la respuesta aceptada, ya que esta fue la respuesta exacta a la pregunta del OP.
SRy
Gracias, @ResponseBody era lo que necesitaba
riskop
¿Curioso cuál es la posición "mejor" para @ResponseBody antes o después de la palabra clave pública? Siempre lo he puesto después, ya que está más identificado con el valor de retorno.
David Bradley
26

En un proyecto abordamos esto usando JSONObject ( información de dependencia de Maven ). Elegimos esto porque preferimos devolver una cadena simple en lugar de un objeto contenedor. Una clase auxiliar interna podría usarse fácilmente en su lugar si no desea agregar una nueva dependencia.

Ejemplo de uso:

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return JSONObject.quote("Hello World");
    }
}
La daga de Gilbert Arenas
fuente
1
Tal vez debería mencionar en su respuesta, que "\"Hello World\""funcionaría igual de bien sin la dependencia adicional, eso es lo que JSONObject.quote()hace, ¿verdad?
jerico
No me gusta la solución, pero funcionó para mí. :-)
Michael Hegner
22

Puede regresar fácilmente JSONcon la Stringpropiedad de la responsesiguiente manera

@RestController
public class TestController {
    @RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map getString() {
        return Collections.singletonMap("response", "Hello World");
    }
}
Javasick
fuente
2
cada vez que use '@RestController', no necesita usar '@ResponseBody'
jitendra varshney
12

Simplemente anule el registro de la StringHttpMessageConverterinstancia predeterminada :

@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.stream()
      .filter(c -> c instanceof StringHttpMessageConverter)
      .findFirst().ifPresent(converters::remove);
  }
}

Probado con métodos de controlador de acciones de controlador y controladores de excepción de controlador:

@RequestMapping("/foo")
public String produceFoo() {
  return "foo";
}

@ExceptionHandler(FooApiException.class)
public String fooException(HttpServletRequest request, Throwable e) {
  return e.getMessage();
}

Notas finales

  • extendMessageConvertersestá disponible desde Spring 4.1.3, si se está ejecutando en una versión anterior, puede implementar la misma técnica usando configureMessageConverters, solo requiere un poco más de trabajo.
  • Este fue un enfoque de muchos otros enfoques posibles, si su aplicación solo devuelve JSON y ningún otro tipo de contenido, es mejor omitir los convertidores predeterminados y agregar un solo convertidor jackson. Otro enfoque es agregar los convertidores predeterminados pero en un orden diferente para que el convertidor jackson sea anterior al de la cadena. Esto debería permitir que los métodos de acción del controlador dicten cómo quieren que se convierta la Cadena dependiendo del tipo de medio de la respuesta.
Amr Mostafa
fuente
1
Sería bueno tener un código de ejemplo con respecto a su segunda nota final.
Tony Baguette
1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
chrylis -cautiouslyoptimistic-
10

Sé que esta pregunta es antigua, pero también me gustaría contribuir:

La principal diferencia entre otras respuestas es el retorno del hashmap.

@GetMapping("...")
@ResponseBody
public Map<String, Object> endPointExample(...) {

    Map<String, Object> rtn = new LinkedHashMap<>();

    rtn.put("pic", image);
    rtn.put("potato", "King Potato");

    return rtn;

}

Esto devolverá:

{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}
Brenno Leal
fuente
2
¿Por qué declaras que el método devuelve un HashMap? LHM implementa Map.
JL_SO
6

Hacer simple:

    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        LOG.info("REST request health check");
        return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
    }
samarone
fuente
Usar una ResponseEntity parece ser lo más avanzado para mí. +1
Alexander
5

Añadir produces = "application/json"en la @RequestMappinganotación como:

@RequestMapping(value = "api/login", method = RequestMethod.GET, produces = "application/json")

Sugerencia: como valor de retorno, recomiendo usar ResponseEntity<List<T>>type. Debido a que los datos producidos en el cuerpo JSON deben ser una matriz o un objeto de acuerdo con sus especificaciones, en lugar de una sola cadena simple . A veces puede causar problemas (por ejemplo, Observables en Angular2).

Diferencia:

regresó Stringcomo json:"example"

regresó List<String>como json:["example"]

Aybars Yuksel
fuente
3

Agregue una @ResponseBodyanotación, que escribirá los datos de retorno en la secuencia de salida.

hugo
fuente
1
Esto no funcionó para mí. Tengo@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
aliopi
0

Este problema me ha vuelto loco: Spring es una herramienta tan potente y, sin embargo, algo tan simple como escribir una cadena de salida como JSON parece imposible sin hacks feos.

Mi solución (en Kotlin) que encuentro menos intrusiva y más transparente es usar un consejo de controlador y verificar si la solicitud fue a un conjunto particular de puntos finales (API REST normalmente, ya que a menudo queremos devolver TODAS las respuestas de aquí como JSON y no hacer especializaciones en el frontend en función de si los datos devueltos son una cadena simple ("¡No deserialización JSON!") o algo más ("¡Deserialización JSON!")). El aspecto positivo de esto es que el controlador sigue siendo el mismo y sin hacks.

El supportsmétodo se asegura de que todas las solicitudes que fueron manejadas por StringHttpMessageConverter(por ejemplo, el convertidor que maneja la salida de todos los controladores que devuelven cadenas simples) se procesen y en el beforeBodyWritemétodo, controlamos en qué casos queremos interrumpir y convertir la salida a JSON (y modificar los encabezados en consecuencia).

@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
    }
}

Espero que en el futuro obtengamos una anotación simple en la que podamos anular la que HttpMessageConverterdebería usarse para la salida.

reflejos rápidos
fuente
-5

Agregue esta anotación a su método

@RequestMapping(value = "/getString", method = RequestMethod.GET, produces = "application/json")
Dali
fuente