Spring MVC - Como retornar String simples como JSON no Rest Controller

137

Minha pergunta é essencialmente um acompanhamento para essa pergunta.

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

Acima, o Spring adicionaria "Hello World" ao corpo da resposta. Como posso retornar uma String como uma resposta JSON? Entendo que poderia adicionar aspas, mas isso parece mais um hack.

Forneça quaisquer exemplos para ajudar a explicar esse conceito.

Nota: não quero que isso seja gravado diretamente no corpo da resposta HTTP, quero retornar a String no formato JSON (estou usando meu Controller com RestyGWT, que exige que a resposta esteja no formato JSON válido).

Adaga de Gilbert Arenas
fonte
Você pode retornar Mapa ou qualquer objeto / entidade que contêm a cadeia
Denys Denysiuk
Quer dizer que você deseja que o valor String seja serializado em uma string JSON?
Sotirios Delimanolis

Respostas:

150

Retornar text/plain(como em Retornar apenas mensagem de string do Spring MVC 3 Controller ) OU envolver sua String é algum objeto

public class StringResponse {

    private String response;

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

    // get/set omitted...
}


Defina seu tipo de resposta para MediaType.APPLICATION_JSON_VALUE(= "application/json")

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

e você terá um JSON que se parece

{  "response" : "your string value" }
Shaun
fonte
124
Você também pode retornar Collections.singletonMap("response", "your string value")para obter o mesmo resultado sem precisar criar uma classe de wrapper.
Bohuslav Burghardt
@Bohuslav Essa é uma ótima dica.
Shaun
6
Não é verdade que exija uma chave e um valor. Uma única String ou uma matriz de strings são JSON válidas. Se você discordar, talvez possa explicar por que o site jsonlint aceita esses dois como JSON válido.
KyleM
2
como a classe wrapper é convertida em um JSON?
Rocky Inde
3
Eu acho que é o suficiente para retornoCollections.singleton("your string value")
gauee
54

JSON é essencialmente uma String no contexto PHP ou JAVA. Isso significa que a string JSON válida pode ser retornada em resposta. A seguir deve 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}";
  }

Isso é bom para uma resposta simples de string. Mas para uma resposta JSON complexa, você deve usar a classe wrapper conforme descrito por Shaun.

pinkal vansia
fonte
7
Essa resposta deve ser aceita, pois foi a resposta exata à pergunta do OP.
SRY
Obrigado, @ResponseBody era o que eu precisava
riskop
Curioso, qual é a posição "melhor" para @ResponseBody antes ou depois da palavra-chave pública? Eu sempre o coloquei depois, pois é mais identificado com o valor de retorno.
David Bradley
26

Em um projeto, abordamos isso usando JSONObject (maven dependency info ). Nós escolhemos isso porque preferimos retornar uma String simples em vez de um objeto wrapper. Uma classe auxiliar interna pode ser facilmente usada se você não quiser adicionar uma nova dependência.

Exemplo de uso:

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return JSONObject.quote("Hello World");
    }
}
Adaga de Gilbert Arenas
fonte
1
Talvez você deva mencionar na sua resposta que "\"Hello World\""isso funcionaria tão bem sem a dependência extra - é isso que JSONObject.quote()funciona, certo?
jerico
Não gosto da solução, mas funcionou para mim. :-)
Michael Hegner
22

Você pode retornar facilmente JSONcom a Stringpropriedade da responseseguinte maneira

@RestController
public class TestController {
    @RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map getString() {
        return Collections.singletonMap("response", "Hello World");
    }
}
Javasick
fonte
2
sempre que você usa '@RestController', você não precisa usar '@ResponseBody'
jitendra varshney
12

Simplesmente cancele o registro da StringHttpMessageConverterinstância padrão :

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

Testado com os métodos do manipulador de ações do controlador e com os manipuladores de exceção do controlador:

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

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

Notas finais:

  • extendMessageConvertersestá disponível desde o Spring 4.1.3. Se estiver sendo executado em uma versão anterior, você poderá implementar a mesma técnica usando configureMessageConverters, apenas será necessário um pouco mais de trabalho.
  • Essa foi uma abordagem de muitas outras abordagens possíveis; se seu aplicativo retornar apenas JSON e nenhum outro tipo de conteúdo, é melhor ignorar os conversores padrão e adicionar um único conversor jackson. Outra abordagem é adicionar os conversores padrão, mas em ordem diferente, de modo que o conversor jackson seja anterior à string um. Isso deve permitir que os métodos de ação do controlador determinem como eles desejam que a String seja convertida, dependendo do tipo de mídia da resposta.
Amr Mostafa
fonte
1
Seria bom ter um código de exemplo referente à sua 2ª nota final.
Tony Baguette
1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
chrylis -cautiouslyoptimistic-
10

Sei que esta pergunta é antiga, mas gostaria de contribuir também:

A principal diferença entre as outras respostas é o retorno do 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;

}

Isso retornará:

{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}
Brenno Leal
fonte
2
Por que você está declarando o método como retornando um HashMap? O LHM implementa o Mapa.
JL_SO 5/01/19
6

Faça simples:

    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        LOG.info("REST request health check");
        return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
    }
samarone
fonte
Usar um ResponseEntity parece ser o estado da arte para mim. +1
Alexander
5

Adicionar produces = "application/json"na @RequestMappinganotação como:

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

Dica: como valor de retorno, recomendo usar o ResponseEntity<List<T>>tipo. Como os dados produzidos no corpo JSON precisam ser uma matriz ou um objeto de acordo com suas especificações, em vez de um simples sequência . Às vezes, pode causar problemas (por exemplo, Observáveis ​​no Angular2).

Diferença:

retornou Stringcomo json:"example"

retornado List<String>como json:["example"]

Aybars Yuksel
fonte
3

Adicione @ResponseBodyanotação, que gravará dados de retorno no fluxo de saída.

Hugo
fonte
1
isso não funcionou para mim. Eu tenho@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
aliopi
0

Esse problema me deixou louco: o Spring é uma ferramenta tão potente e, no entanto, uma coisa tão simples como escrever uma String de saída como o JSON parece impossível sem hacks feios.

Minha solução (no Kotlin) que eu acho menos intrusiva e transparente é usar um conselho de controlador e verificar se a solicitação foi para um conjunto específico de pontos de extremidade (API REST normalmente, pois geralmente queremos retornar TODAS as respostas daqui como JSON e não faça especializações no front-end com base em se os dados retornados são uma sequência simples ("Não faça desserialização de JSON!") ou outra coisa ("Faça desserialização de JSON!")). O aspecto positivo disso é que o controlador permanece o mesmo e sem hacks.

O supportsmétodo garante que todas as solicitações que foram tratadas pelo StringHttpMessageConverter(por exemplo, o conversor que lida com a saída de todos os controladores que retornam strings simples) sejam processadas e, no beforeBodyWritemétodo, controlamos em quais casos queremos interromper e converter a saída em JSON (e modifique os cabeçalhos de acordo).

@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, no futuro, recebamos uma anotação simples na qual possamos substituir o que HttpMessageConverterdeve ser usado para a saída.

reflexos rápidos
fonte
-5

Adicione esta anotação ao seu método

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