Spring MVC - Zurückgeben eines einfachen Strings als JSON in Rest Controller

137

Meine Frage ist im Wesentlichen eine Fortsetzung dieser Frage.

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

Oben würde Spring "Hello World" zum Antworttext hinzufügen. Wie kann ich einen String als JSON-Antwort zurückgeben? Ich verstehe, dass ich Zitate hinzufügen könnte, aber das fühlt sich eher wie ein Hack an.

Bitte geben Sie Beispiele an, um dieses Konzept zu erläutern.

Hinweis: Ich möchte nicht, dass dies direkt in den HTTP-Antworttext geschrieben wird. Ich möchte den String im JSON-Format zurückgeben (ich verwende meinen Controller mit RestyGWT, für den die Antwort im gültigen JSON-Format vorliegen muss).

Der Gilbert Arenas Dolch
quelle
Sie können Map oder jedes Objekt / jede Entität, die Ihre Zeichenfolge enthält, zurückgeben
Denys Denysiuk
Sie möchten also, dass der String-Wert in einen JSON-String serialisiert wird?
Sotirios Delimanolis

Antworten:

150

Entweder return text/plain(wie in Nur String-Nachricht von Spring MVC 3 Controller zurückgeben ) oder Wrap Ihr ​​String ist ein Objekt

public class StringResponse {

    private String response;

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

    // get/set omitted...
}


Setzen Sie Ihren Antworttyp auf MediaType.APPLICATION_JSON_VALUE(= "application/json")

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

und Sie haben einen JSON, der aussieht wie

{  "response" : "your string value" }
Shaun
quelle
124
Sie können auch zurückkehren Collections.singletonMap("response", "your string value"), um dasselbe Ergebnis zu erzielen, ohne eine Wrapper-Klasse erstellen zu müssen.
Bohuslav Burghardt
@Bohuslav Das ist ein toller Tipp.
Shaun
6
Es ist nicht wahr, dass es einen Schlüssel und einen Wert erfordert. Ein einzelner String oder ein Array von Strings sind beide gültige JSON. Wenn Sie nicht einverstanden sind, können Sie vielleicht erklären, warum die jsonlint-Website beide als gültigen JSON akzeptiert.
KyleM
2
Wie wird die Wrapper-Klasse in einen JSON konvertiert?
Rocky Inde
3
Ich denke, es ist genug, um zurückzukehrenCollections.singleton("your string value")
gauee
54

JSON ist im Wesentlichen ein String im PHP- oder JAVA-Kontext. Dies bedeutet, dass eine gültige JSON-Zeichenfolge als Antwort zurückgegeben werden kann. Folgendes sollte funktionieren.

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

Dies ist für eine einfache Zeichenfolgenantwort in Ordnung. Für komplexe JSON-Antworten sollten Sie jedoch die von Shaun beschriebene Wrapper-Klasse verwenden.

Pinkal Vansia
quelle
7
Dies sollte als Antwort akzeptiert werden, da dies die genaue Antwort auf die Frage des OP war.
SRy
Danke, @ResponseBody war das, was ich brauchte
riskop
Neugierig, welche Position "besser" für @ResponseBody vor oder nach dem öffentlichen Schlüsselwort ist? Ich habe es immer nachgestellt, da es mehr mit dem Rückgabewert identifiziert ist.
David Bradley
26

In einem Projekt haben wir dies mit JSONObject (Maven Dependency Info ) behoben . Wir haben uns dafür entschieden, weil wir lieber einen einfachen String als ein Wrapper-Objekt zurückgegeben haben. Stattdessen kann problemlos eine interne Hilfsklasse verwendet werden, wenn Sie keine neue Abhängigkeit hinzufügen möchten.

Anwendungsbeispiel:

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return JSONObject.quote("Hello World");
    }
}
Der Gilbert Arenas Dolch
quelle
1
Vielleicht sollten Sie in Ihrer Antwort erwähnen, dass "\"Hello World\""dies ohne die zusätzliche Abhängigkeit genauso gut funktionieren würde - genau das JSONObject.quote()tut es, richtig?
Jerico
Ich mag die Lösung nicht, aber sie hat bei mir funktioniert. :-)
Michael Hegner
22

Sie können JSONmit Stringin Eigenschaft responsewie folgt einfach zurückkehren

@RestController
public class TestController {
    @RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map getString() {
        return Collections.singletonMap("response", "Hello World");
    }
}
Javasick
quelle
2
Wenn Sie '@RestController' verwenden, müssen Sie '@ResponseBody' nicht verwenden
jitendra varshney
12

Heben Sie einfach die Registrierung der Standardinstanz auf StringHttpMessageConverter:

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

Getestet mit Controller Action Handler-Methoden und Controller Exception Handlern:

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

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

Schlussbemerkungen:

  • extendMessageConvertersist seit Spring 4.1.3 verfügbar. Wenn Sie eine frühere Version verwenden, mit der Sie dieselbe Technik implementieren können configureMessageConverters, ist nur ein wenig mehr Arbeit erforderlich .
  • Dies war einer von vielen anderen möglichen Ansätzen. Wenn Ihre Anwendung immer nur JSON und keine anderen Inhaltstypen zurückgibt, ist es besser, die Standardkonverter zu überspringen und einen einzelnen Jackson-Konverter hinzuzufügen. Ein anderer Ansatz besteht darin, die Standardkonverter in unterschiedlicher Reihenfolge hinzuzufügen, sodass der Jackson-Konverter vor dem String-Konverter steht. Auf diese Weise sollten Controller-Aktionsmethoden festlegen können, wie String abhängig vom Medientyp der Antwort konvertiert werden soll.
Amr Mostafa
quelle
1
Es wäre schön, einen Beispielcode für Ihre 2. letzte Notiz zu haben.
Tony Baguette
1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
Chrylis
10

Ich weiß, dass diese Frage alt ist, aber ich möchte auch dazu beitragen:

Der Hauptunterschied zwischen anderen Antworten ist die Hashmap-Rückgabe.

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

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

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

    return rtn;

}

Dies wird zurückkehren:

{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}
Brenno Leal
quelle
2
Warum deklarieren Sie die Methode als Rückgabe einer HashMap? LHM implementiert Map.
JL_SO
6

Machen Sie es einfach:

    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        LOG.info("REST request health check");
        return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
    }
Samarone
quelle
Die Verwendung einer ResponseEntity scheint mir Stand der Technik zu sein. +1
Alexander
5

Fügen Sie produces = "application/json"in @RequestMappingAnmerkung wie:

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

Hinweis: Als Rückgabewert empfehle ich die Verwendung von ResponseEntity<List<T>>type. Weil die erzeugten Daten im JSON-Body ein Array oder ein Objekt gemäß seinen Spezifikationen sein müssen und nicht eine einzelne einfache Zeichenfolge . Dies kann manchmal zu Problemen führen (z. B. Observables in Angular2).

Unterschied:

zurückgegeben Stringals json:"example"

zurückgegeben List<String>als json:["example"]

Aybars Yuksel
quelle
3

Fügen Sie eine @ResponseBodyAnmerkung hinzu, die Rückgabedaten in den Ausgabestream schreibt.

Hugo
quelle
1
Das hat bei mir nicht funktioniert. Ich habe@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
Aliopi
0

Dieses Problem hat mich verrückt gemacht: Spring ist ein so mächtiges Werkzeug und doch scheint eine so einfache Sache wie das Schreiben eines Ausgabe-Strings wie JSON ohne hässliche Hacks unmöglich.

Meine Lösung (in Kotlin), die ich am wenigsten aufdringlich und am transparentesten finde, besteht darin, einen Controller-Rat zu verwenden und zu überprüfen, ob die Anforderung an einen bestimmten Satz von Endpunkten gesendet wurde (REST-API normalerweise, da wir von hier aus am häufigsten ALLE Antworten als JSON zurückgeben möchten und machen Sie keine Spezialisierungen im Frontend, basierend darauf, ob die zurückgegebenen Daten eine einfache Zeichenfolge ("JSON-Deserialisierung nicht durchführen!") oder etwas anderes ("JSON-Deserialisierung durchführen!") sind. Der positive Aspekt dabei ist, dass der Controller derselbe und ohne Hacks bleibt.

Das supports Methode stellt sicher, dass alle Anforderungen, die vom verarbeitet wurden StringHttpMessageConverter(z. B. der Konverter, der die Ausgabe aller Controller verarbeitet, die einfache Zeichenfolgen zurückgeben), verarbeitet werden. In der beforeBodyWriteMethode steuern wir, in welchen Fällen wir die Ausgabe unterbrechen und in JSON konvertieren möchten (und ändern Sie die Header entsprechend).

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

Ich hoffe, dass wir in Zukunft eine einfache Anmerkung erhalten, in der wir überschreiben können, welche HttpMessageConverterfür die Ausgabe verwendet werden soll.

schnelle Reflexe
quelle
-5

Fügen Sie diese Anmerkung Ihrer Methode hinzu

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