Spring MVC - Cách trả về Chuỗi đơn giản dưới dạng JSON trong Trình điều khiển nghỉ


137

Câu hỏi của tôi về cơ bản là theo dõi câu hỏi này .

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

Ở phần trên, Spring sẽ thêm "Hello World" vào phần phản hồi. Làm cách nào tôi có thể trả về Chuỗi dưới dạng phản hồi JSON? Tôi hiểu rằng tôi có thể thêm dấu ngoặc kép, nhưng cảm giác đó giống như một bản hack.

Vui lòng cung cấp bất kỳ ví dụ để giúp giải thích khái niệm này.

Lưu ý: Tôi không muốn điều này được viết thẳng vào phần thân Phản hồi HTTP, tôi muốn trả về Chuỗi ở định dạng JSON (Tôi đang sử dụng Trình điều khiển của mình với RestyGWT , yêu cầu phản hồi phải ở định dạng JSON hợp lệ).


Bạn có thể trả về Bản đồ hoặc bất kỳ đối tượng / thực thể nào chứa chuỗi của bạn
Denys Denysiuk

Vậy ý bạn là bạn muốn giá trị Chuỗi được nối tiếp thành chuỗi JSON?
Sotirios Delimanolis

Câu trả lời:


150

Trả về text/plain(như trong Trả về thông báo chuỗi từ Bộ điều khiển Spring MVC 3 ) HOẶC bọc Chuỗi của bạn là một số đối tượng

public class StringResponse {

    private String response;

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

    // get/set omitted...
}


Đặt loại phản hồi của bạn thành MediaType.APPLICATION_JSON_VALUE(= "application/json")

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

và bạn sẽ có một JSON giống như

{  "response" : "your string value" }

124
Bạn cũng có thể quay lại Collections.singletonMap("response", "your string value")để đạt được kết quả tương tự mà không cần phải tạo lớp bao bọc.
Bohuslav Burghardt

@Bohuslav Đó là một mẹo tuyệt vời.
Shaun

6
Điều đó không đúng khi yêu cầu khóa và giá trị. Một chuỗi đơn hoặc một chuỗi các chuỗi đều là JSON hợp lệ. Nếu bạn không đồng ý, có lẽ bạn có thể giải thích lý do tại sao trang web jsonlint chấp nhận cả hai thứ đó là JSON hợp lệ.
KyleM

2
Làm thế nào để lớp trình bao bọc được chuyển đổi thành JSON?
Rocky Inde

3
Tôi nghĩ rằng nó là đủ để trở vềCollections.singleton("your string value")
gauee

54

JSON thực chất là một Chuỗi trong ngữ cảnh PHP hoặc JAVA. Điều đó có nghĩa là chuỗi JSON hợp lệ có thể được trả về. Theo sau nên làm việc.

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

Điều này là ổn cho phản ứng chuỗi đơn giản. Nhưng đối với phản hồi JSON phức tạp, bạn nên sử dụng lớp trình bao bọc như được mô tả bởi Shaun.


7
Điều này nên được chấp nhận câu trả lời, vì đây là câu trả lời chính xác cho câu hỏi của OP.
SRy

Cảm ơn, @ResponseBody là những gì tôi cần
riskop

Bạn có tò mò vị trí "tốt hơn" nào cho @ResponseBody trước hoặc sau từ khóa công khai không? Tôi đã luôn đặt nó sau, vì nó được xác định nhiều hơn với giá trị trả về.
David Bradley

26

Trong một dự án, chúng tôi đã giải quyết vấn đề này bằng JSONObject ( thông tin phụ thuộc maven ). Chúng tôi đã chọn điều này bởi vì chúng tôi muốn trả về một Chuỗi đơn giản hơn là một đối tượng trình bao bọc. Một lớp trợ giúp nội bộ có thể dễ dàng được sử dụng thay thế nếu bạn không muốn thêm một phụ thuộc mới.

Cách sử dụng ví dụ:

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

1
Có lẽ bạn nên đề cập đến trong câu trả lời của mình, điều đó "\"Hello World\""cũng sẽ hoạt động tốt với sự phụ thuộc thêm - đó là những gì JSONObject.quote(), phải không?
jerico

Tôi không thích giải pháp, nhưng nó hiệu quả với tôi. :-)
Michael Hegner

22

Bạn có thể dễ dàng trở lại JSONvới Stringtài sản responsenhư sau

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

2
Bất cứ khi nào bạn sử dụng '@RestControll', bạn không cần sử dụng '@ResponseBody'
varshney

12

Đơn giản chỉ cần hủy đăng ký mặc định 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);
  }
}

Đã thử nghiệm với cả hai phương thức xử lý hành động của trình điều khiển và các trình xử lý ngoại lệ của trình điều khiển:

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

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

Ghi chú cuối cùng:

  • extendMessageConverterscó sẵn kể từ Spring 4.1.3, nếu đang chạy trên phiên bản trước, bạn có thể thực hiện kỹ thuật tương tự bằng cách sử dụng configureMessageConverters, nó chỉ mất thêm một chút công việc.
  • Đây là một cách tiếp cận của nhiều cách tiếp cận khả thi khác, nếu ứng dụng của bạn chỉ trả về JSON và không có loại nội dung nào khác, tốt hơn hết là bỏ qua các trình chuyển đổi mặc định và thêm một trình chuyển đổi jackson duy nhất. Một cách tiếp cận khác là thêm các bộ chuyển đổi mặc định nhưng theo thứ tự khác nhau để bộ chuyển đổi jackson được đặt trước chuỗi thứ nhất. Điều này sẽ cho phép các phương thức hành động của bộ điều khiển đưa ra cách chúng muốn chuyển đổi Chuỗi tùy thuộc vào loại phương tiện của phản hồi.

1
Sẽ thật tuyệt nếu có một mã ví dụ liên quan đến ghi chú cuối cùng thứ 2 của bạn.
Tony Baguette

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

10

Tôi biết rằng câu hỏi này đã cũ nhưng tôi cũng muốn đóng góp:

Sự khác biệt chính giữa các phản hồi khác là trả về 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;

}

Điều này sẽ trở lại:

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

2
Tại sao bạn khai báo phương thức là trả về HashMap? LHM thực hiện Bản đồ.
JL_SO

6

Làm đơn giản:

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

Sử dụng một FeedbackEntity dường như là trạng thái của nghệ thuật đối với tôi. +1
Alexander

5

Thêm produces = "application/json"vào @RequestMappingchú thích như sau:

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

Gợi ý: Là một giá trị trả về, tôi khuyên bạn nên sử dụng ResponseEntity<List<T>>loại. Bởi vì dữ liệu được tạo ra trong phần thân JSON cần phải là một mảng hoặc một đối tượng theo thông số kỹ thuật của nó, chứ không phải là một chuỗi đơn giản . Nó có thể gây ra vấn đề đôi khi (ví dụ: Đài quan sát trong Angular2).

Sự khác biệt:

trở lại Stringnhư json:"example"

trở lại List<String>như json:["example"]


3

Thêm @ResponseBodychú thích, sẽ ghi dữ liệu trả về trong luồng đầu ra.


1
điều này đã không làm việc cho tôi. Tôi có@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
aliopi

0

Vấn đề này đã khiến tôi phát điên: Spring là một công cụ mạnh mẽ và tuy nhiên, một điều đơn giản như viết một chuỗi đầu ra như JSON dường như là không thể nếu không có các bản hack xấu xí.

Giải pháp của tôi (trong Kotlin) mà tôi thấy ít xâm phạm và minh bạch nhất là sử dụng lời khuyên của bộ điều khiển và kiểm tra xem yêu cầu có đi đến một bộ điểm cuối cụ thể không (API REST thường vì chúng tôi thường muốn trả về TẤT CẢ các câu trả lời từ đây dưới dạng JSON và không thực hiện các chuyên môn trong frontend dựa trên việc dữ liệu được trả về có phải là một chuỗi đơn giản ("Đừng thực hiện khử tuần tự JSON!") hay một cái gì khác ("Thực hiện khử tuần tự JSON!")). Khía cạnh tích cực của điều này là bộ điều khiển vẫn giữ nguyên và không có hack.

Các supportsphương pháp làm cho chắc chắn rằng tất cả các yêu cầu đã được xử lý bởi các StringHttpMessageConverter(ví dụ như bộ chuyển đổi mà xử lý đầu ra của tất cả các bộ điều khiển mà trở về chuỗi đồng bằng) được xử lý và trong beforeBodyWritephương pháp, chúng tôi kiểm soát, trong đó trường hợp chúng ta muốn làm gián đoạn và chuyển đổi đầu ra để JSON (và sửa đổi các tiêu đề cho phù hợp).

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

Tôi hy vọng trong tương lai chúng ta sẽ có được một chú thích đơn giản trong đó chúng ta có thể ghi đè HttpMessageConverternên được sử dụng cho đầu ra.


-5

Thêm chú thích này vào phương thức của bạn

@RequestMapping(value = "/getString", method = RequestMethod.GET, produces = "application/json")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.