Spring MVC - Как вернуть простую строку как JSON в Rest Controller


137

Мой вопрос по сути является продолжением этого вопроса.

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

Выше Spring добавил бы «Hello World» в тело ответа. Как я могу вернуть строку в качестве ответа JSON? Я понимаю, что могу добавить цитаты, но это больше похоже на взлом.

Пожалуйста, предоставьте любые примеры, которые помогут объяснить эту концепцию.

Примечание: я не хочу, чтобы это было записано прямо в тело ответа HTTP, я хочу вернуть строку в формате JSON (я использую мой контроллер с RestyGWT, который требует, чтобы ответ был в допустимом формате JSON).


Вы можете вернуть карту или любой объект / объект, содержащий вашу строку
Денис Денисюк

То есть вы хотите, чтобы значение String было сериализовано в строку JSON?
Сотириос Делиманолис

Ответы:


150

Либо верните text/plain(как в Return только строковое сообщение из 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")для достижения того же результата, не создавая класс-оболочку.
Богуслав Бургардт

@ Богуслав Это отличный совет.
Шон

6
Это не правда, что для этого нужны ключ и значение. Одна строка или массив строк являются допустимыми JSON. Если вы не согласны, может быть, вы можете объяснить, почему веб-сайт jsonlint принимает оба из них в качестве действительного JSON.
KyleM

2
Как класс-оболочка конвертируется в JSON?
Рокки Инд

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 был тем, что мне было нужно
рископ

Любопытно, что является «лучшей» позицией для @ResponseBody до или после ключевого слова public? Я всегда помещал это после, так как это больше идентифицируется с возвращаемым значением.
Дэвид Брэдли,

26

В одном проекте мы решили эту проблему с помощью JSONObject ( информация о зависимостях maven ). Мы выбрали это, потому что мы предпочитали возвращать простую строку, а не объект-оболочку. Вместо этого можно легко использовать внутренний вспомогательный класс, если вы не хотите добавлять новую зависимость.

Пример использования:

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

1
Может быть, вы должны упомянуть в своем ответе, что это "\"Hello World\""будет работать так же хорошо, без дополнительной зависимости - это то JSONObject.quote(), что работает, верно?
Джерико

Мне не нравится решение, но оно сработало для меня. :-)
Майкл Хегнер

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доступен начиная с Spring 4.1.3, если вы работаете в предыдущей версии, вы можете реализовать ту же технику, используя configureMessageConverters, это займет немного больше работы.
  • Это был один из многих возможных подходов, если ваше приложение возвращает только JSON и никаких других типов контента, вам лучше пропустить конвертеры по умолчанию и добавить один конвертер Джексона. Другой подход заключается в добавлении преобразователей по умолчанию, но в другом порядке, чтобы преобразователь Джексона был перед строковым. Это должно позволить методам действия контроллера определять, как они должны преобразовывать строку в зависимости от типа носителя ответа.

1
Было бы неплохо иметь пример кода относительно вашей второй заключительной записки.
Тони Багет

1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
chrylis -cautiouslyoptimistic-

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 реализует карту.
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 должны быть массивом или объектом в соответствии с их спецификациями, а не одной простой строкой . Это может иногда вызывать проблемы (например, Observables в Angular2).

Разница:

вернулся Stringкак json:"example"

вернулся List<String>как json:["example"]


3

Добавьте @ResponseBodyаннотацию, которая будет записывать возвращаемые данные в выходной поток.


1
это не сработало для меня. У меня@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
Алиопи

0

Эта проблема привела меня в бешенство: Spring - такой мощный инструмент, и все же такая простая вещь, как написание выходной строки в виде JSON, кажется невозможной без уродливых хаков.

Мое решение (в Kotlin), которое я считаю наименее навязчивым и наиболее прозрачным, состоит в том, чтобы использовать совет контроллера и проверить, был ли запрос направлен на определенный набор конечных точек (обычно 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.