Spring MVC - Comment renvoyer une chaîne simple en tant que JSON dans Rest Controller

137

Ma question fait essentiellement suite à cette question.

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

Dans ce qui précède, Spring ajouterait "Hello World" dans le corps de la réponse. Comment puis-je renvoyer une chaîne en tant que réponse JSON? Je comprends que je pourrais ajouter des citations, mais cela ressemble plus à un hack.

Veuillez fournir des exemples pour expliquer ce concept.

Remarque: je ne veux pas que cela soit écrit directement dans le corps de la réponse HTTP, je veux renvoyer la chaîne au format JSON (j'utilise mon contrôleur avec RestyGWT qui nécessite que la réponse soit au format JSON valide).

Le poignard Gilbert Arenas
la source
Vous pouvez renvoyer Map ou tout objet / entité contenant votre chaîne
Denys Denysiuk
Vous voulez dire que vous voulez que la valeur String soit sérialisée en une chaîne JSON?
Sotirios Delimanolis

Réponses:

150

Soit return text/plain(comme dans Return only string message from Spring MVC 3 Controller ) OU envelopper votre String est un objet

public class StringResponse {

    private String response;

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

    // get/set omitted...
}


Définissez votre type de réponse sur MediaType.APPLICATION_JSON_VALUE(= "application/json")

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

et vous aurez un JSON qui ressemble à

{  "response" : "your string value" }
Shaun
la source
124
Vous pouvez également revenir Collections.singletonMap("response", "your string value")pour obtenir le même résultat sans avoir à créer une classe wrapper.
Bohuslav Burghardt
@Bohuslav C'est un bon conseil.
Shaun
6
Ce n'est pas vrai que cela nécessite une clé et une valeur. Une seule chaîne ou un tableau de chaînes sont tous deux des JSON valides. Si vous n'êtes pas d'accord, vous pouvez peut-être expliquer pourquoi le site Web jsonlint accepte les deux comme JSON valide.
KyleM
2
comment la classe wrapper est-elle convertie en JSON?
Rocky Inde
3
Je pense qu'il suffit de revenirCollections.singleton("your string value")
gauee
54

JSON est essentiellement une chaîne dans un contexte PHP ou JAVA. Cela signifie que la chaîne qui est valide JSON peut être retournée en réponse. La suite devrait fonctionner.

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

C'est correct pour une réponse de chaîne simple. Mais pour une réponse JSON complexe, vous devez utiliser la classe wrapper comme décrit par Shaun.

pinkal vansia
la source
7
Cette réponse devrait être acceptée, car c'était la réponse exacte à la question du PO.
SRy
Merci, @ResponseBody était ce dont j'avais besoin
Riskop
Curieux de savoir quelle est la "meilleure" position pour @ResponseBody avant ou après le mot-clé public? Je l'ai toujours mis après, car il est plus identifié avec la valeur de retour.
David Bradley
26

Dans un projet, nous avons résolu ce problème en utilisant JSONObject ( maven dependency info ). Nous avons choisi cela car nous avons préféré renvoyer une chaîne simple plutôt qu'un objet wrapper. Une classe d'assistance interne pourrait facilement être utilisée à la place si vous ne souhaitez pas ajouter de nouvelle dépendance.

Exemple d'utilisation:

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return JSONObject.quote("Hello World");
    }
}
Le poignard Gilbert Arenas
la source
1
Peut-être devriez-vous mentionner dans votre réponse que "\"Hello World\""cela fonctionnerait aussi bien sans la dépendance supplémentaire - c'est ce qui JSONObject.quote()fonctionne, non?
jerico
Je n'aime pas la solution, mais cela a fonctionné pour moi. :-)
Michael Hegner
22

Vous pouvez facilement revenir JSONavec Stringdans la propriété responsecomme suit

@RestController
public class TestController {
    @RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map getString() {
        return Collections.singletonMap("response", "Hello World");
    }
}
Javasick
la source
2
chaque fois que vous utilisez '@RestController', vous n'avez pas besoin d'utiliser '@ResponseBody'
jitendra varshney
12

Désenregistrez simplement l' StringHttpMessageConverterinstance par défaut :

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

Testé avec les méthodes de gestionnaire d'action de contrôleur et les gestionnaires d'exception de contrôleur:

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

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

Notes finales:

  • extendMessageConverters est disponible depuis le printemps 4.1.3, si vous utilisez une version précédente, vous pouvez implémenter la même technique en utilisant configureMessageConverters , cela prend juste un peu plus de travail.
  • C'était une approche parmi de nombreuses autres approches possibles, si votre application ne renvoie que JSON et aucun autre type de contenu, il vaut mieux ignorer les convertisseurs par défaut et ajouter un seul convertisseur jackson. Une autre approche consiste à ajouter les convertisseurs par défaut mais dans un ordre différent afin que le convertisseur jackson soit avant celui de la chaîne. Cela devrait permettre aux méthodes d'action du contrôleur de dicter la manière dont elles souhaitent que String soit converti en fonction du type de média de la réponse.
Amr Mostafa
la source
1
Ce serait bien d'avoir un exemple de code concernant votre deuxième note finale.
Tony Baguette
1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
chrylis -cautouslyoptimistic-
10

Je sais que cette question est ancienne mais j'aimerais aussi contribuer:

La principale différence entre les autres réponses est le retour de 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;

}

Cela retournera:

{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}
Brenno Leal
la source
2
Pourquoi déclarez-vous la méthode comme renvoyant un HashMap? LHM implémente Map.
JL_SO
6

Faites simple:

    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        LOG.info("REST request health check");
        return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
    }
samarone
la source
L'utilisation d'un ResponseEntity me semble être un état de l'art . +1
Alexander le
5

Ajouter produces = "application/json"dans l' @RequestMappingannotation comme:

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

Astuce: comme valeur de retour, je recommande d'utiliser le ResponseEntity<List<T>>type. Parce que les données produites dans le corps JSON doivent être un tableau ou un objet selon ses spécifications, plutôt qu'une simple chaîne simple . Cela peut parfois causer des problèmes (par exemple Observables dans Angular2).

Différence:

renvoyé Stringcomme json:"example"

renvoyé List<String>comme json:["example"]

Aybars Yuksel
la source
3

Ajoutez une @ResponseBodyannotation, qui écrira les données de retour dans le flux de sortie.

hugo
la source
1
cela n'a pas fonctionné pour moi. J'ai@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
aliopi
0

Ce problème m'a rendu fou: Spring est un outil si puissant et pourtant, une chose aussi simple que d'écrire une chaîne de sortie en JSON semble impossible sans des hacks horribles.

Ma solution (dans Kotlin) que je trouve la moins intrusive et la plus transparente est d'utiliser un conseil de contrôleur et de vérifier si la requête est allée à un ensemble particulier de points de terminaison (API REST généralement car nous voulons le plus souvent renvoyer TOUTES les réponses d'ici au format JSON et ne pas faire de spécialisations dans le frontend en fonction du fait que les données renvoyées sont une chaîne simple ("Ne pas faire de désérialisation JSON!") ou autre chose ("Faire une désérialisation JSON!")). L'aspect positif de ceci est que le contrôleur reste le même et sans hacks.

La supportsméthode s'assure que toutes les requêtes qui ont été traitées par le StringHttpMessageConverter(par exemple le convertisseur qui gère la sortie de tous les contrôleurs qui renvoient des chaînes en clair) sont traitées et dans la beforeBodyWriteméthode, nous contrôlons dans quels cas nous voulons interrompre et convertir la sortie en JSON (et modifiez les en-têtes en conséquence).

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

J'espère qu'à l'avenir nous obtiendrons une annotation simple dans laquelle nous pourrons remplacer celle qui HttpMessageConverterdevrait être utilisée pour la sortie.

réflexes rapides
la source
-5

Ajoutez cette annotation à votre méthode

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