【问题标题】:Spring MVC - How to return simple String as JSON in Rest ControllerSpring MVC - 如何在 Rest Controller 中以 JSON 形式返回简单字符串
【发布时间】:2015-09-02 21:46:06
【问题描述】:

我的问题本质上是this 问题的后续问题。

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

在上面,Spring 会将“Hello World”添加到响应正文中。如何将字符串作为 JSON 响应返回?我知道我可以添加引号,但感觉更像是一种 hack。

请提供任何示例来帮助解释这个概念。

注意:我不希望将其直接写入 HTTP 响应正文,我想以 JSON 格式返回字符串(我正在使用我的控制器 RestyGWT 要求响应为有效 JSON 格式)。

【问题讨论】:

  • 您可以返回 Map 或任何包含您的字符串的对象/实体
  • 那么您的意思是您希望将 String 值序列化为 JSON 字符串?

标签: java json spring rest spring-mvc


【解决方案1】:

要么返回text/plain(如Return only string message from Spring MVC 3 Controller)要么包装你的字符串是一些对象

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

【讨论】:

  • 您也可以返回Collections.singletonMap("response", "your string value") 以实现相同的结果,而无需创建包装类。
  • 它需要一个键和一个值是不正确的。单个字符串或字符串数​​组都是有效的 JSON。如果你不同意,也许你可以解释为什么 jsonlint 网站接受这两个作为有效的 JSON。
  • 当然是。 json.org 确认。
  • 包装类如何转换为 JSON?
  • 我觉得回Collections.singleton("your string value")就够了
【解决方案2】:

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 描述的包装类。

【讨论】:

  • 这应该被接受,因为这是对 OP 问题的确切答案。
  • 谢谢,@ResponseBody 正是我所需要的
  • 好奇 @ResponseBody 在 public 关键字之前或之后哪个“更好”的位置?我总是把它放在后面,因为它更符合返回值。
  • 但是我的回复中出现了这个转义斜线。奇怪的事情
【解决方案3】:

在一个项目中,我们使用 JSONObject (maven dependency info) 解决了这个问题。我们选择这个是因为我们更喜欢返回一个简单的 String 而不是一个包装对象。如果您不想添加新的依赖项,可以轻松地使用内部帮助程序类。

示例用法:

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

【讨论】:

  • 也许你应该在回答中提到,"\"Hello World\"" 在没有额外依赖的情况下也能正常工作 - 这就是 JSONObject.quote() 所做的,对吧?
  • 我不喜欢这个解决方案,但它对我有用。 :-)
【解决方案4】:

您可以在属性response 中使用String 轻松返回JSON,如下所示

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

【讨论】:

  • 当你使用'@RestController'时,你不需要使用'@ResponseBody'
【解决方案5】:

只需注销默认的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.removeIf(c -> c instanceof StringHttpMessageConverter);
  }
}

使用控制器操作处理程序方法和控制器异常处理程序进行测试:

@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 而没有其他内容类型,您最好跳过默认转换器并添加单个杰克逊转换器。另一种方法是添加默认转换器,但顺序不同,以便杰克逊转换器在字符串之前。这应该允许控制器操作方法根据响应的媒体类型来决定他们希望如何转换 String。

【讨论】:

  • 如果有关于您的第二个最终注释的示例代码会很高兴。
  • converters.removeIf(c -&gt; c instanceof StringHttpMessageConverter)
  • 在我看来这是唯一正确的答案,因为最初的问题是如何将一个简单的字符串写成 json,而不是一个对象或任何其他很容易工作的东西。多亏了这个回复,我学到了一些新东西。
  • 删除消息转换器可能会产生不希望的副作用。例如。它使用 springdoc 破坏了 Swagger-UI,springdoc 将 JSON 作为字符串输出,并依赖于转换程序来取消引用它。 (作为参考,错误消息是“无法呈现此定义提供的定义未指定有效的版本字段。”)。
  • 虽然这确实有效,但我建议不要这样做,因为稍后在添加第三方库时可能会遇到问题,这些库添加了返回已编码的 json 字符串的端点。它将由 Jackson 消息转换器再次编码并产生您不想要的结果。
【解决方案6】:

我知道这个问题很老,但我也想贡献:

其他响应之间的主要区别是哈希图返回。

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

【讨论】:

    【解决方案7】:

    简单:

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

    【讨论】:

    • 使用 ResponseEntity 对我来说似乎是最先进的。 +1
    【解决方案8】:

    @RequestMapping注解中添加produces = "application/json",如:

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

    提示:作为返回值,我推荐使用ResponseEntity&lt;List&lt;T&gt;&gt; 类型。因为 JSON body 中生成的数据需要根据其规范是一个 array 或一个 object,而不是单个简单的 string。有时可能会导致问题(例如 Angular2 中的 Observables)。

    区别:

    以 json 格式返回 String"example"

    以 json 格式返回 List&lt;String&gt;["example"]

    【讨论】:

    • @GetMapping(value = "/version") public ResponseEntity&lt;?&gt; version() { return ResponseEntity.ok(VERSION); } 不起作用,仍然发送纯字符串而不是 json。
    【解决方案9】:

    添加@ResponseBody注解,将返回数据写入输出流。

    【讨论】:

    • 这对我不起作用。我有@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
    【解决方案10】:

    这个问题把我逼疯了:Spring 是一个非常强大的工具,然而,如果没有丑陋的 hack,将输出字符串编写为 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 用于输出。

    【讨论】:

    • 不错。如果你有多个端点返回一个字符串,其中一些已经在 json 中(很可能来自 3rd 方库),我认为这是最好的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-02
    • 2014-06-28
    • 2015-07-24
    • 2014-01-06
    • 2012-09-26
    • 2013-03-08
    • 2018-12-08
    相关资源
    最近更新 更多