Spring MVC - Come restituire una stringa semplice come JSON in Rest Controller


137

La mia domanda è essenzialmente un seguito a questa domanda.

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

In quanto sopra, Spring aggiungerebbe "Hello World" nel corpo della risposta. Come posso restituire una stringa come risposta JSON? Capisco che potrei aggiungere delle virgolette, ma sembra più un hack.

Fornisci eventuali esempi per aiutare a spiegare questo concetto.

Nota: non voglio che questo sia scritto direttamente nel corpo della risposta HTTP, voglio restituire la stringa in formato JSON (sto usando il mio controller con RestyGWT che richiede che la risposta sia in formato JSON valido).


Puoi restituire Mappa o qualsiasi oggetto / entità che contenga la tua stringa
Denys Denysiuk,

Quindi vuoi dire che vuoi che il valore String sia serializzato su una stringa JSON?
Sotirios Delimanolis,

Risposte:


150

O return text/plain(come in Return only string message from Spring MVC 3 Controller ) OPPURE avvolgere String è un oggetto

public class StringResponse {

    private String response;

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

    // get/set omitted...
}


Imposta il tipo di risposta su MediaType.APPLICATION_JSON_VALUE(= "application/json")

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

e avrai un JSON che sembra

{  "response" : "your string value" }

124
Potresti anche tornare Collections.singletonMap("response", "your string value")per ottenere lo stesso risultato senza dover creare una classe wrapper.
Bohuslav Burghardt,

@ Bohuslav Questo è un ottimo consiglio.
Shaun,

6
Non è vero che richiede una chiave e un valore. Una singola stringa o un array di stringhe sono entrambi JSON validi. Se non sei d'accordo, forse puoi spiegare perché il sito Web jsonlint accetta entrambi come JSON validi.
KyleM,

2
come viene convertita la classe wrapper in un JSON?
Rocky Inde,

3
Penso che sia sufficiente tornareCollections.singleton("your string value")
gauee

54

JSON è essenzialmente una stringa in contesto PHP o JAVA. Ciò significa che una stringa valida JSON può essere restituita in risposta. Di seguito dovrebbe funzionare.

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

Questo va bene per la semplice risposta di stringa. Ma per una risposta JSON complessa dovresti usare la classe wrapper come descritto da Shaun.


7
Questa dovrebbe essere una risposta accettata, poiché questa era la risposta esatta alla domanda del PO.
SRy,

Grazie, @ResponseBody era quello di cui avevo bisogno
riskop

Curioso qual è la posizione "migliore" per @ResponseBody prima o dopo la parola chiave pubblica? L'ho sempre messo dopo, poiché è più identificato con il valore di ritorno.
David Bradley,

26

In un progetto che abbiamo affrontato questo utilizzando JSONObject (Maven informazioni di dipendenza ). Abbiamo scelto questo perché preferivamo restituire una stringa semplice anziché un oggetto wrapper. È possibile utilizzare facilmente una classe di supporto interna se non si desidera aggiungere una nuova dipendenza.

Esempio di utilizzo:

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

1
Forse dovresti menzionare nella tua risposta, che "\"Hello World\""funzionerebbe altrettanto bene senza la dipendenza extra - è quello che JSONObject.quote()fa, giusto?
jerico,

Non mi piace la soluzione, ma ha funzionato per me. :-)
Michael Hegner,

22

Puoi facilmente tornare JSONcon Stringin proprietà responsecome segue

@RestController
public class TestController {
    @RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map getString() {
        return Collections.singletonMap("response", "Hello World");
    }
}

2
ogni volta che usi '@RestController', non devi usare '@ResponseBody'
jitendra varshney

12

Annullare semplicemente la registrazione StringHttpMessageConverterdell'istanza predefinita :

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

Testato sia con i metodi del gestore dell'azione del controller sia con i gestori delle eccezioni del controller:

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

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

Note finali:

  • extendMessageConvertersè disponibile dalla primavera 4.1.3, se si esegue una versione precedente è possibile implementare la stessa tecnica utilizzando configureMessageConverters, richiede solo un po 'più di lavoro.
  • Questo era un approccio di molti altri possibili approcci, se l'applicazione restituisce JSON e nessun altro tipo di contenuto, è meglio saltare i convertitori predefiniti e aggiungere un singolo convertitore jackson. Un altro approccio è quello di aggiungere i convertitori predefiniti, ma in un ordine diverso in modo che il convertitore jackson sia precedente a quello della stringa. Ciò dovrebbe consentire ai metodi di azione del controller di stabilire come convertire String in base al tipo di supporto della risposta.

1
Sarebbe bello avere un codice di esempio relativo alla seconda nota finale.
Tony Baguette,

1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
Chrylis

10

So che questa domanda è vecchia ma vorrei contribuire anche io:

La differenza principale tra le altre risposte è il ritorno 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;

}

Questo restituirà:

{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}

2
Perché stai dichiarando il metodo come restituzione di una HashMap? LHM implementa la mappa.
JL_SO,

6

Semplifica:

    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        LOG.info("REST request health check");
        return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
    }

L'uso di ResponseEntity mi sembra all'avanguardia . +1
Alexander

5

Aggiungere produces = "application/json"in @RequestMappingannotazioni come:

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

Suggerimento: come valore di ritorno, consiglio di usare il ResponseEntity<List<T>>tipo. Perché i dati prodotti nel corpo JSON devono essere una matrice o un oggetto in base alle sue specifiche, piuttosto che una singola stringa semplice . A volte può causare problemi (ad esempio osservabili in Angular2).

Differenza:

restituito Stringcome json:"example"

restituito List<String>come json:["example"]


3

Aggiungi @ResponseBodyannotazione, che scriverà i dati di ritorno nel flusso di output.


1
questo non ha funzionato per me. Ho@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
aliopi il

0

Questo problema mi ha fatto impazzire: Spring è uno strumento così potente e tuttavia, una cosa così semplice come scrivere una stringa di output come JSON sembra impossibile senza brutti hack.

La mia soluzione (in Kotlin) che trovo meno invadente e più trasparente è quella di utilizzare un consiglio del controller e verificare se la richiesta è andata a un determinato set di endpoint (API REST in genere poiché spesso vogliamo restituire TUTTE le risposte da qui come JSON e non effettuare specializzazioni nel frontend in base al fatto che i dati restituiti siano una stringa semplice ("Non eseguire la deserializzazione JSON!") o qualcos'altro ("Esegui la deserializzazione JSON!")). L'aspetto positivo di questo è che il controller rimane lo stesso e senza hack.

Il supportsmetodo assicura che tutte le richieste gestite dal StringHttpMessageConverter(ad es. Il convertitore che gestisce l'output di tutti i controller che restituiscono stringhe semplici) siano elaborate e nel beforeBodyWritemetodo, controlliamo in quali casi vogliamo interrompere e convertire l'output in JSON (e modificare le intestazioni di conseguenza).

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

Spero in futuro di ottenere una semplice annotazione in cui possiamo eseguire l'override che HttpMessageConverterdovrebbe essere utilizzata per l'output.


-5

Aggiungi questa annotazione al tuo metodo

@RequestMapping(value = "/getString", method = RequestMethod.GET, produces = "application/json")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.