การส่งคืนออบเจ็กต์ JSON เป็นการตอบสนองใน Spring Boot


88

ฉันมี RestController ตัวอย่างใน Spring Boot:

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

ฉันใช้ไลบรารี JSON org.json

เมื่อฉันกด API /helloฉันได้รับข้อยกเว้นว่า:

Servlet.service () สำหรับ servlet [dispatcherServlet] ในบริบทที่มีพา ธ [] ข้อยกเว้นที่ถูกโยน [การประมวลผลคำขอล้มเหลว; ข้อยกเว้นที่ซ้อนกันคือ java.lang.IllegalArgumentException: ไม่พบตัวแปลงสำหรับค่าส่งคืนของประเภท: class org.json.JSONObject] ที่มีสาเหตุ

java.lang.IllegalArgumentException: ไม่พบตัวแปลงสำหรับค่าส่งคืนของประเภท: class org.json.JSONObject

ปัญหาคืออะไร? ใครช่วยอธิบายได้ว่าเกิดอะไรขึ้น?


Jackson ไม่สามารถแปลง JSONObject เป็น json
โป

โอเคฉันเข้าใจแล้ว สิ่งที่สามารถทำได้ในการแก้ไขปัญหานี้คืออะไร?
iwekesi

1
ฉันต้องการให้การตอบสนองถูกสร้างขึ้นทันที ฉันไม่ต้องการสร้างคลาสเฉพาะสำหรับการตอบกลับแต่ละครั้ง
iwekesi

2
อาจจะดีกว่าถ้าให้ method ของคุณกลับมาเป็น String นอกจากนี้คุณยังสามารถเพิ่มคำอธิบายประกอบ @ResponseBody ต่อท้ายวิธีการนี้จะจัดการกับคำตอบของคุณตามที่ร้องขอ :-)@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}
vegaasen

@vegaasen คุณสามารถอธิบายเพิ่มเติมเกี่ยวกับ ResponseBody
iwekesi

คำตอบ:


109

ในขณะที่คุณใช้เว็บ Spring Boot การพึ่งพาแจ็คสันนั้นมีความหมายโดยนัยและเราไม่จำเป็นต้องกำหนดอย่างชัดเจน คุณสามารถตรวจสอบการพึ่งพาแจ็คสันในpom.xmlแท็บลำดับชั้นการพึ่งพาของคุณหากใช้ eclipse

และตามที่คุณได้ใส่คำอธิบายประกอบไปด้วย@RestControllerไม่จำเป็นต้องทำการแปลง json อย่างชัดเจน เพียงส่งคืน POJO และเครื่องอนุกรมแจ็คสันจะดูแลการแปลงเป็น json เทียบเท่ากับการใช้@ResponseBodyเมื่อใช้กับ @Controller แทนที่จะวาง@ResponseBodyในทุกวิธีการควบคุมที่เราวาง@RestControllerแทนวานิลลา@Controllerและ@ResponseBodyโดยค่าเริ่มต้นจะใช้กับทรัพยากรทั้งหมดในตัวควบคุมนั้น
อ้างอิงลิงค์นี้: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

ปัญหาที่คุณพบคือเนื่องจากวัตถุที่ส่งคืน (JSONObject) ไม่มี getter สำหรับคุณสมบัติบางอย่าง และความตั้งใจของคุณคือจะไม่ทำให้ JSONObject นี้เป็นอนุกรม แต่จะทำให้เป็นลำดับ POJO แทน ดังนั้นเพียงส่งคืน POJO
อ้างอิงลิงค์นี้: https://stackoverflow.com/a/35822500/5039001

หากคุณต้องการส่งคืนสตริงอนุกรม json ให้ส่งคืนสตริง Spring จะใช้ StringHttpMessageConverter แทนตัวแปลง JSON ในกรณีนี้


ถ้าสตริง json คือสิ่งที่คุณต้องการส่งคืนจาก java คุณก็สามารถส่งคืนสตริงได้หากมันเป็นอนุกรม json แล้ว ไม่จำเป็นต้องแปลงสตริงเป็น json และ json กลับเป็นสตริง
prem kumar

6
หากคุณต้องการส่งคืนชุดของคู่ค่าชื่อ - ค่าโดยไม่มีโครงสร้างเวลาคอมไพล์ที่เข้มงวดคุณสามารถส่งคืน a Map<String,Object>หรือPropertiesวัตถุ
Vihung

@prem kumar สุ่มคำถาม: คุณหมายถึงอะไรกับ 'แทน vanilla Controller และ ResponseBody'? วานิลลาที่นี่คืออะไร?
Orkun Ozen

ฉันหมายถึงคอนโทรลเลอร์ตามปกติและมีคำอธิบายประกอบ ResponseBody อยู่ในทุกวิธีการร้องขอ
prem kumar

68

สาเหตุที่แนวทางปัจจุบันของคุณใช้ไม่ได้เนื่องจาก Jackson ถูกใช้โดยค่าเริ่มต้นเพื่อทำให้เป็นอนุกรมและเพื่อแยกวัตถุ อย่างไรก็ตามไม่ทราบวิธีการจัดลำดับไฟล์JSONObject. หากคุณต้องการสร้างโครงสร้าง JSON แบบไดนามิกคุณสามารถใช้ a Mapตัวอย่างเช่น:

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

สิ่งนี้จะนำไปสู่การตอบสนอง JSON ต่อไปนี้:

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

มีข้อ จำกัด เล็กน้อยเนื่องจากการเพิ่มวัตถุลูกอาจยากขึ้นเล็กน้อย แจ็คสันมีกลไกของตัวเองโดยใช้ObjectNodeและArrayNode. ในการใช้งานคุณต้องกำหนดเส้นทางอัตโนมัติObjectMapperในบริการ / ตัวควบคุมของคุณ จากนั้นคุณสามารถใช้:

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

วิธีนี้ช่วยให้คุณสามารถเพิ่มวัตถุลูกอาร์เรย์และใช้ทุกประเภทได้


2
mapper ที่นี่คืออะไร?
iwekesi

1
@iwekesi นั่นคือแจ็คสันที่ObjectMapperคุณควรอัตโนมัติ (ดูย่อหน้าเหนือข้อมูลโค้ดสุดท้ายของฉัน)
g00glen00b

1
เป็นเรื่องที่น่าทึ่งมากที่รู้ว่าต้องใช้ความยาวมากขนาดนั้นเพื่อสร้างวัตถุ JSON ที่มีความหมาย เป็นเรื่องน่าเศร้าที่ Pivotal ไม่ได้ใช้ความพยายามเลย ( spring.io/guides/gs/actuator-service ) เพื่อเรียกข้อ จำกัด เหล่านี้ออกไป โชคดีที่เรามีดังนั้น! ;)
cogitoergosum

@HikaruShindo java.util.HashMapเป็นฟังก์ชันหลักของ Java ตั้งแต่ Java 1.2
g00glen00b

44

คุณสามารถส่งคืนการตอบกลับตามStringที่ @vagaasen แนะนำหรือใช้ResponseEntityObject ที่ Spring จัดเตรียมไว้ด้านล่าง ด้วยวิธีนี้คุณยังสามารถส่งคืนHttp status codeซึ่งมีประโยชน์มากกว่าในการโทรผ่านเว็บบริการ

@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
ถ้าฉันเพิ่ม JSONObject ในเอนทิตีมันเป็นข้อยกเว้นที่คล้ายกันอีกครั้ง
iwekesi

@Sangam คำตอบของคุณช่วยฉันสำหรับปัญหาอื่นที่เกี่ยวข้องกับ jackson-dataformat-xml
พระเจ้า

นี่เป็นความช่วยเหลือที่ยิ่งใหญ่! ขอขอบคุณ!
jones-chris

1
ฉันสงสัยว่าทำไมคำตอบนี้จึงไม่ได้รับการโหวตเพิ่มเติม ฉันยังใหม่กับ Spring ด้วยดังนั้นฉันไม่รู้ว่านี่เป็นแนวทางปฏิบัติด้านวิศวกรรมซอฟต์แวร์ที่ดี คำตอบนี้ช่วยฉันได้จริงๆ อย่างไรก็ตามฉันมีปัญหากับ a JSONObjectมาก แต่เนื่องจาก Spring ใช้ Jackson ฉันจึงเปลี่ยนเป็น a HashMapแทนจากนั้นรหัสที่ฉันอ่านในคำตอบนี้ก็ใช้งานได้
Melvin Roest

2
+1 สำหรับการแนะนำ MediaType.APPLICATION_JSON_VALUE เนื่องจากทำให้มั่นใจได้ว่าผลลัพธ์ที่สร้างจะได้รับการแยกวิเคราะห์เป็น json ไม่ใช่ xml อย่างที่อาจเกิดขึ้นหากคุณไม่ได้กำหนด
Sandeep Mandori

11

คุณยังสามารถใช้แฮชแมปสำหรับสิ่งนี้

@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());
}

ปล. ใช้ได้กับ 1 ค่าเท่านั้น


3

ใช้ ResponseEntity<ResponseBean>

ที่นี่คุณสามารถใช้ ResponseBean หรือ Any java bean ได้ตามที่คุณต้องการเพื่อส่งคืนการตอบสนอง api ของคุณและเป็นแนวทางปฏิบัติที่ดีที่สุด ฉันใช้ Enum เพื่อตอบสนอง มันจะส่งคืนรหัสสถานะและข้อความสถานะของ 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);
    }

สำหรับการตอบสนอง ServiceStatus หรือ (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 ควรมีคีย์ด้านล่างในการตอบสนอง

  1. รหัสสถานะ
  2. ข้อความ

คุณจะได้รับคำตอบขั้นสุดท้ายด้านล่าง

{

   "StatusCode" : "0",

   "Message":"Login success"

}

คุณสามารถใช้ ResponseBody (java POJO, ENUM ฯลฯ ) ตามความต้องการของคุณ


2

สร้าง DTO ที่ถูกต้องมากขึ้นสำหรับการสืบค้น API ตัวอย่างเช่น entityDTO:

  1. การตอบสนองเริ่มต้นตกลงพร้อมรายการเอนทิตี:
@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
    return entityService.getAllEntities();
}

แต่ถ้าคุณต้องการส่งคืนพารามิเตอร์แผนที่ที่แตกต่างกันคุณสามารถใช้สองตัวอย่างถัดไป
สำหรับการส่งคืนพารามิเตอร์หนึ่งรายการเช่นแผนที่:

@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
    return ResponseEntity.status(HttpStatus.CREATED).body(
            Collections.singletonMap("key", "value"));
}
  1. และหากคุณต้องการแผนที่ส่งคืนของพารามิเตอร์บางตัว (ตั้งแต่ 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

หากคุณต้องการส่งคืนออบเจ็กต์ JSON โดยใช้ String สิ่งต่อไปนี้ควรใช้งานได้:

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);
    }
    ...
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.