Trả về đối tượng JSON dưới dạng phản hồi trong Spring Boot


85

Tôi có một RestController mẫu trong Spring Boot:

@RestController
@RequestMapping("/api")
class MyRestController
{
    @GetMapping(path = "/hello")
    public JSONObject sayHello()
    {
        return new JSONObject("{'aa':'bb'}");
    }
}

Tôi đang sử dụng thư viện JSON org.json

Khi tôi nhấn API /hello, tôi nhận được một ngoại lệ nói:

Servlet.service () cho servlet [dispatcherServlet] trong ngữ cảnh với đường dẫn [] đã ném ngoại lệ [Xử lý yêu cầu không thành công; ngoại lệ lồng nhau là java.lang.IllegalArgumentException: Không tìm thấy trình chuyển đổi nào cho giá trị trả về của loại: class org.json.JSONObject] với nguyên nhân gốc

java.lang.IllegalArgumentException: Không tìm thấy trình chuyển đổi nào cho giá trị trả về của kiểu: class org.json.JSONObject

Vấn đề là gì? Ai đó có thể giải thích chính xác những gì đang xảy ra?


Jackson không thể chuyển đổi JSONObject thành json.
Pau

Ok, tôi hiểu điều đó. Có thể làm gì để sửa lỗi này?
iwekesi

1
Tôi muốn phản hồi được xây dựng nhanh chóng. Tôi không muốn tạo các lớp cụ thể cho mỗi phản hồi.
iwekesi

2
Có thể tốt hơn nếu chỉ trả về phương thức của bạn dưới dạng Chuỗi. Ngoài ra, bạn cũng có thể thêm các chú thích để @ResponseBody phương pháp này, điều này sẽ xử lý trả lời của bạn theo yêu cầu :-)@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}
vegaasen

@vegaasen bạn có thể xây dựng một chút về ResponseBody
iwekesi

Câu trả lời:


103

Khi bạn đang sử dụng web Spring Boot, sự phụ thuộc của Jackson là ẩn và chúng ta không cần phải xác định rõ ràng. Bạn có thể kiểm tra sự phụ thuộc Jackson trong pom.xmltab của bạn trong tab thứ bậc phụ thuộc nếu sử dụng eclipse.

Và như bạn đã chú thích @RestController, không cần thực hiện chuyển đổi json rõ ràng. Chỉ cần trả lại một POJO và bộ nối tiếp jackson sẽ thực hiện việc chuyển đổi sang json. Nó tương đương với việc sử dụng @ResponseBodykhi sử dụng với @Controller. Thay vì đặt @ResponseBodytrên mọi phương thức bộ điều khiển, chúng tôi đặt @RestControllerthay vì vani @Controller@ResponseBodytheo mặc định được áp dụng trên tất cả các tài nguyên trong bộ điều khiển đó.
Tham khảo liên kết này: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

Vấn đề bạn đang gặp phải là do đối tượng trả về (JSONObject) không có getter cho các thuộc tính nhất định. Và mục đích của bạn không phải là tuần tự hóa JSONObject này mà thay vào đó là tuần tự hóa một POJO. Vì vậy, chỉ cần trả lại POJO.
Tham khảo liên kết này: https://stackoverflow.com/a/35822500/5039001

Nếu bạn muốn trả về một chuỗi được tuần tự hóa json thì chỉ cần trả về chuỗi. Spring sẽ sử dụng StringHttpMessageConverter thay vì trình chuyển đổi JSON trong trường hợp này.


nếu chuỗi json là những gì bạn muốn trả về từ java thì bạn chỉ có thể trả về một chuỗi nếu nó đã được tuần tự hóa json. Không cần chuyển đổi chuỗi thành json và json trở lại chuỗi.
Prem kumar

5
Nếu bạn muốn trả lại một tập hợp các cặp tên-giá trị mà không có một cấu trúc thời gian biên dịch cứng nhắc, bạn có thể trả về một Map<String,Object>hoặc một Propertiesđối tượng
Vihung

@prem kumar câu hỏi ngẫu nhiên: ý bạn là gì với 'thay vì vani Controller và ResponseBody'? vani ở đây là gì?
Orkun Ozen

ý tôi là một Bộ điều khiển thông thường và với chú thích ResponseBody được đặt trên mọi phương thức yêu cầu.
Prem kumar

66

Lý do tại sao cách tiếp cận hiện tại của bạn không hoạt động là do Jackson được sử dụng theo mặc định để tuần tự hóa và giải mã hóa các đối tượng. Tuy nhiên, nó không biết làm thế nào để tuần tự hóa JSONObject. Nếu bạn muốn tạo cấu trúc JSON động, bạn có thể sử dụng Map, ví dụ:

@GetMapping
public Map<String, String> sayHello() {
    HashMap<String, String> map = new HashMap<>();
    map.put("key", "value");
    map.put("foo", "bar");
    map.put("aa", "bb");
    return map;
}

Điều này sẽ dẫn đến phản hồi JSON sau:

{ "key": "value", "foo": "bar", "aa": "bb" }

Điều này có một chút hạn chế, vì việc thêm các đối tượng con có thể trở nên khó khăn hơn một chút. Jackson có cơ chế riêng của mình, sử dụng ObjectNodeArrayNode. Để sử dụng nó, bạn phải tự động phát tín hiệu ObjectMappertrong dịch vụ / bộ điều khiển của mình. Sau đó, bạn có thể sử dụng:

@GetMapping
public ObjectNode sayHello() {
    ObjectNode objectNode = mapper.createObjectNode();
    objectNode.put("key", "value");
    objectNode.put("foo", "bar");
    objectNode.put("number", 42);
    return objectNode;
}

Cách tiếp cận này cho phép bạn thêm các đối tượng con, mảng và sử dụng tất cả các kiểu khác nhau.


2
người lập bản đồ ở đây là gì?
iwekesi

1
@iwekesi đó là Jackson ObjectMappermà bạn nên tự động phát (xem đoạn trên đoạn mã cuối cùng của tôi).
g00glen00b

1
Thật ngạc nhiên khi biết rằng người ta phải trải qua nhiều thời gian như vậy để tạo ra các đối tượng JSON có ý nghĩa! Cũng đáng buồn là Pivotal không nỗ lực gì cả ( spring.io/guides/gs/actuator-service ) để chỉ ra những hạn chế này. May mắn thay, chúng tôi có SO! ;)
cogitoergosum

Không thể giải quyết việc import java.util.HashMap
Hikaru Shindo

@HikaruShindo java.util.HashMaplà một chức năng cốt lõi của Java kể từ Java 1.2.
g00glen00b

43

Bạn có thể trả lại câu trả lời theo Stringđề xuất của @vagaasen hoặc bạn có thể sử dụng ResponseEntityĐối tượng do Spring cung cấp như bên dưới. Bằng cách này, bạn cũng có thể quay lại Http status codeđiều này hữu ích hơn trong việc gọi dịch vụ web.

@RestController
@RequestMapping("/api")
public class MyRestController
{

    @GetMapping(path = "/hello", produces=MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> sayHello()
    {
         //Get data from service layer into entityList.

        List<JSONObject> entities = new ArrayList<JSONObject>();
        for (Entity n : entityList) {
            JSONObject entity = new JSONObject();
            entity.put("aa", "bb");
            entities.add(entity);
        }
        return new ResponseEntity<Object>(entities, HttpStatus.OK);
    }
}

1
Nếu tôi thêm JSONObject vào đơn vị, người ta một lần nữa đem lại cho tôi tương tự ngoại lệ
iwekesi

@Sangam câu trả lời của bạn đã giúp tôi cho một vấn đề khác liên quan đến jackson-dataFormat-xml
thần thánh

Đây là một trợ giúp lớn! Cảm ơn bạn!
jones-chris

1
Tôi tự hỏi tại sao câu trả lời này không được ủng hộ nhiều hơn. Tôi cũng mới làm quen với Spring, vì vậy tôi không biết liệu đây có phải là một thực hành kỹ thuật phần mềm tốt hay không. Với điều đó đã nói, câu trả lời này thực sự giúp ích cho tôi. Tuy nhiên, tôi đã gặp rất nhiều rắc rối với a JSONObject, nhưng vì Spring sử dụng Jackson nên tôi đã đổi thành a để HashMapthay thế và sau đó mã mà tôi đọc trong câu trả lời này đã hoạt động.
Melvin Roest

2
1 cho thấy các MediaType.APPLICATION_JSON_VALUE vì nó đảm bảo rằng kết quả sản xuất được phân tích cú pháp như json không xml như có thể xảy ra nếu bạn không xác định
Sandeep Mandori

11

bạn cũng có thể sử dụng một bản đồ băm cho việc này

@GetMapping
public HashMap<String, Object> get() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("results", somePOJO);
    return map;
}

6
@RequestMapping("/api/status")
public Map doSomething()
{
    return Collections.singletonMap("status", myService.doSomething());
}

Tái bút. Chỉ hoạt động cho 1 giá trị


3

sử dụng ResponseEntity<ResponseBean>

Ở đây bạn có thể sử dụng ResponseBean hoặc Any java bean tùy thích để trả về phản hồi api của mình và đó là cách tốt nhất. Tôi đã sử dụng Enum để phản hồi. nó sẽ trả về mã trạng thái và thông báo trạng thái của API.

@GetMapping(path = "/login")
public ResponseEntity<ServiceStatus> restApiExample(HttpServletRequest request,
            HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        loginService.login(username, password, request);
        return new ResponseEntity<ServiceStatus>(ServiceStatus.LOGIN_SUCCESS,
                HttpStatus.ACCEPTED);
    }

để phản hồi ServiceStatus hoặc (ResponseBody)

    public enum ServiceStatus {

    LOGIN_SUCCESS(0, "Login success"),

    private final int id;
    private final String message;

    //Enum constructor
    ServiceStatus(int id, String message) {
        this.id = id;
        this.message = message;
    }

    public int getId() {
        return id;
    }

    public String getMessage() {
        return message;
    }
}

Spring REST API phải có khóa bên dưới để phản hồi

  1. Mã trạng thái
  2. Thông điệp

bạn sẽ nhận được phản hồi cuối cùng bên dưới

{

   "StatusCode" : "0",

   "Message":"Login success"

}

bạn có thể sử dụng ResponseBody (java POJO, ENUM, v.v.) theo yêu cầu của bạn.


2

Đúng hơn, hãy tạo DTO cho các truy vấn API, ví dụ như entityDTO:

  1. Phản hồi mặc định OK với danh sách các thực thể:
@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
    return entityService.getAllEntities();
}

Nhưng nếu bạn cần trả về các tham số Bản đồ khác nhau, bạn có thể sử dụng hai ví dụ tiếp theo
2. Để trả về một tham số như bản đồ:

@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
    return ResponseEntity.status(HttpStatus.CREATED).body(
            Collections.singletonMap("key", "value"));
}
  1. Và nếu bạn cần bản đồ trả về của một số tham số (kể từ Java 9):
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getSomeParameters() {
    return ResponseEntity.status(HttpStatus.OK).body(Map.of(
            "key-1", "value-1",
            "key-2", "value-2",
            "key-3", "value-3"));
}

1

Nếu bạn cần trả về một đối tượng JSON bằng cách sử dụng Chuỗi, thì cách sau sẽ hoạt động:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
...

@RestController
@RequestMapping("/student")
public class StudentController {

    @GetMapping
    @RequestMapping("/")
    public ResponseEntity<JsonNode> get() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode json = mapper.readTree("{\"id\": \"132\", \"name\": \"Alice\"}");
        return ResponseEntity.ok(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.