Spring MVC - Як повернути просту струну як JSON у контрольному режимі відпочинку


137

Моє питання, по суті, є наслідком цього питання.

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

У вищесказаному Весна додала б "Hello World" до органу відповідей. Як я можу повернути рядок як відповідь JSON? Я розумію, що я міг би додати цитати, але це більше схоже на злом.

Наведіть будь-які приклади, які допоможуть пояснити це поняття.

Примітка: я не хочу, щоб це було написано прямо в HTTP Response body, я хочу повернути String у форматі JSON (я використовую свій контролер з RestyGWT, для якого потрібен відповідь у правильному форматі JSON).


Ви можете повернути карту чи будь-який об’єкт / об'єкт, який містить ваш рядок
Denys Denysiuk

Отже, ви маєте на увазі, що хочете, щоб значення String було серіалізовано до рядка JSON?
Сотіріос Деліманоліс

Відповіді:


150

Або поверніть text/plain(як у поверненому лише рядковому повідомленні від Spring MVC 3 Controller ) або оберніть String - це якийсь об'єкт

public class StringResponse {

    private String response;

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

    // get/set omitted...
}


Встановіть тип відповіді MediaType.APPLICATION_JSON_VALUE(= "application/json")

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

і у вас буде JSON, який схожий

{  "response" : "your string value" }

124
Ви також можете повернутися Collections.singletonMap("response", "your string value")для досягнення того ж результату, не створюючи класу обгортки.
Богуслав Бургхардт

@Bohuslav Це чудова порада.
Шон

6
Це неправда, що для нього потрібні ключ і значення. Один рядок або масив рядків є обома дійсними JSON. Якщо ви не згодні, можливо, ви можете пояснити, чому веб-сайт jsonlint приймає обидва з них як дійсні JSON.
KyleM

2
як клас обгортки перетворюється на JSON?
Rocky Inde

3
Я вважаю, що достатньо повернутисяCollections.singleton("your string value")
Gauee

54

JSON по суті є рядком в PHP або JAVA контексті. Це означає, що рядок, яка є дійсною JSON, може бути повернута у відповідь. Слід слід працювати.

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

Це нормально для простої відповіді на рядок. Але для складної відповіді JSON слід використовувати клас обгортки, як описано Shaun.


7
На це слід прийняти відповідь, оскільки це була точна відповідь на питання ОП.
SRy

Дякую, @ResponseBody було те, що мені потрібно
riskop

Цікаво, яка "краща" позиція для @ResponseBody до або після відкритого ключового слова? Я завжди ставив його після, оскільки він більше ототожнюється із значенням повернення.
Девід Бредлі

26

В одному з проектів ми вирішили це за допомогою JSONObject ( інформація про залежність Maven ). Ми вибрали це, тому що віддавали перевагу поверненню простого рядка, а не обертового об’єкта. Замість цього можна легко використовувати внутрішній клас помічників, якщо ви не хочете додавати нову залежність.

Приклад використання:

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

1
Можливо, вам слід сказати у своїй відповіді, що "\"Hello World\""це спрацювало б так само без зайвих залежних - ось що JSONObject.quote()робить, правда?
jerico

Мені не подобається рішення, але воно працювало на мене. :-)
Майкл

22

Ви можете легко повернутися JSONз Stringу власності responseнаступним чином

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

2
щоразу, коли ви використовуєте "@RestController", вам не потрібно використовувати "@ResponseBody"
jitendra varshney

12

Просто скасуйте реєстрацію за замовчуванням 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);
  }
}

Тестовано як методами обробника дій контролера, так і обробниками виключень контролера:

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

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

Заключні записки:

  • extendMessageConvertersдоступний з весни 4.1.3, якщо ви працюєте на попередній версії, ви можете реалізувати ту саму техніку, використовуючи configureMessageConverters, це просто потребує трохи більше роботи.
  • Це було одним із підходів багатьох інших можливих підходів, якщо ваша програма лише повертає JSON та інших типів контенту, вам краще пропустити конвертори за замовчуванням та додати єдиний конвертер Джексона. Інший підхід полягає в тому, щоб додати перетворювачі за замовчуванням, але в іншому порядку, щоб перетворювач Джексона був до строкового. Це повинно дозволяти методам дій контролера диктувати, як вони хочуть перетворити рядок залежно від типу носія відповіді.

1
Було б добре мати приклад коду щодо вашої 2-ї заключної записки.
Тоні Багет

1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
chrylis -обережнооптимістично-

10

Я знаю, що це питання давнє, але я також хотів би зробити свій внесок:

Основна відмінність інших відповідей - повернення хешмапу.

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

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

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

    return rtn;

}

Це поверне:

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

2
Чому ви оголошуєте метод поверненням HashMap? LHM реалізує Map.
JL_SO

6

Зробити просто:

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

Використання ResponseEntity мені здається найсучаснішим . +1
Олександр

5

Додайте produces = "application/json"до @RequestMappingпримітки, як:

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

Підказка: як повернене значення я рекомендую використовувати ResponseEntity<List<T>>тип. Оскільки отримані дані в тілі JSON повинні бути масивом або об'єктом відповідно до його специфікацій, а не однією простою рядком . Іноді це може спричинити проблеми (наприклад, спостерігається в Angular2).

Різниця:

повернуто Stringяк json:"example"

повернуто List<String>як json:["example"]


3

Додайте @ResponseBodyанотацію, яка запише повернення даних у вихідний потік.


1
це не спрацювало для мене. У мене@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
aliopi

0

Це питання мене зводило з розуму: Весна - це такий потужний інструмент, і все-таки така проста річ, як написання вихідного рядка, як JSON, здається неможливим без потворних хак.

Моє рішення (в Котліні) про те, що я вважаю найменш настирливим і найпрозорішим - це використовувати поради контролера і перевірити, чи запит підходив до певного набору кінцевих точок (REST API, зазвичай, оскільки ми найчастіше хочемо повернути ВСІ відповіді звідси як JSON і не проводити спеціалізації у фронтені на основі того, чи є повернені дані простою строкою ("Не робіть десеріалізацію JSON!") чи чимось іншим ("Десеріалізація JSON!"). Позитивним аспектом цього є те, що контролер залишається таким же і без злому.

supportsМетод гарантує , що всі запити , які були оброблені з допомогою StringHttpMessageConverter(наприклад , перетворювача , який обробляє вихідні всіх контролерів , які повертають прості рядки) обробляються і в beforeBodyWriteметоді, ми контролюємо , в яких випадках ми хочемо перервати і перетворити висновок в формат JSON (та змініть заголовки відповідно).

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

Я сподіваюся, що в майбутньому ми отримаємо просту анотацію, в якій ми зможемо перекрити те, яке HttpMessageConverterслід використовувати для отримання результатів.


-5

Додайте цю примітку до свого методу

@RequestMapping(value = "/getString", method = RequestMethod.GET, produces = "application/json")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.