Повернення об'єкта JSON як відповіді у Spring Boot


85

У мене є зразок 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 () для сервлета [depecherServlet] в контексті шляху [], що викинув виняток [Помилка обробки запиту; вкладеним винятком є ​​java.lang.IllegalArgumentException: Не знайдено перетворювача для поверненого значення типу: class org.json.JSONObject] з першопричиною

java.lang.IllegalArgumentException: Не знайдено перетворювача для поверненого значення типу: class org.json.JSONObject

У чому проблема? Хтось може пояснити, що саме відбувається?


Джексон не може перетворити JSONObject на json.
По,

Гаразд, я це розумію. Що можна зробити, щоб це виправити?
iwekesi

1
Я хочу, щоб відповідь була побудована на льоту. Я не хочу створювати конкретні класи для кожної відповіді.
iwekesi

2
Можливо, було б краще, щоб ваш метод повернувся як String. Крім того, ви також можете додати анотацію @ResponseBody до методу, це буде обробляти вашу відповідь за запитом :-)@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}
vegaasen

@vegaasen ви можете трохи детальніше
розповісти

Відповіді:


101

Оскільки ви використовуєте Spring Boot web, залежність Джексона є неявною, і нам не потрібно визначати явно. Ви можете перевірити наявність залежності Джексона у своїй pom.xmlвкладці ієрархії залежностей, якщо використовуєте eclipse.

І як ви вже коментували @RestController, немає необхідності робити явне перетворення json. Просто поверніть POJO, і серіалізатор Джексона подбає про конвертацію в json. Це еквівалентно використанню @ResponseBodyпри використанні з @Controller. Замість того, щоб розміщувати @ResponseBodyна кожному методі контролера, ми розміщуємо його @RestControllerзамість vanilla @Controllerі @ResponseBodyза замовчуванням застосовується до всіх ресурсів цього контролера.
Перейдіть за цим посиланням: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

Проблема, з якою ви стикаєтесь, полягає у тому, що повернутий об’єкт (JSONObject) не має геттера для певних властивостей. І ваш намір полягає не в серіалізації цього JSONObject, а замість цього в серіалізації POJO. Тож просто поверніть POJO.
Перейдіть за цим посиланням: https://stackoverflow.com/a/35822500/5039001

Якщо ви хочете повернути json серіалізований рядок, просто поверніть рядок. Spring в цьому випадку використовуватиме StringHttpMessageConverter замість конвертера JSON.


якщо рядок json - це те, що ви хочете повернути з java, ви можете просто повернути рядок, якщо він уже json серіалізований. Не потрібно перетворювати рядок у json та json назад у рядок.
prem kumar

5
Якщо ви хочете повернути набір пар імен-значення без жорсткої структури часу компіляції, ви можете повернути a Map<String,Object>або Propertiesоб'єкт
Vihung,

Випадкове запитання @prem kumar: що ви маєте на увазі під словом "замість контролера ванілі та ResponseBody"? що тут ванільний?
Оркун Озен

я мав на увазі звичайний контролер та анотацію ResponseBody, розміщену на кожному методі запиту.
prem kumar

65

Причина, по якій ваш поточний підхід не працює, полягає в тому, що Джексон використовується за замовчуванням для серіалізації та десеріалізації об’єктів. Однак він не знає, як серіалізувати JSONObject. Якщо ви хочете створити динамічну структуру JSON, ви можете використовувати 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
що тут картограф?
iwekesi

1
@iwekesi - це Джексон, ObjectMapperякий слід автоматично підключити (див. абзац над моїм останнім фрагментом коду).
g00glen00b

1
Він приголомшуючи знати , що один повинен пройти такі довжини для отримання значущих об'єктів JSON! Також сумно, що Pivotal взагалі не докладає зусиль ( spring.io/guides/gs/actuator-service ), щоб вказати на ці обмеження. На щастя, у нас ТАК! ;)
cogitoergosum

Імпорт java.util.HashMap не може бути вирішений
Hikaru Shindo

@HikaruShindo java.util.HashMap- це основна функціональність Java, починаючи з Java 1.2.
g00glen00b

43

Ви можете повернути відповідь, Stringяк запропонував @vagaasen, або скористатися ResponseEntityоб’єктом, наданим 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і тоді код, який я прочитав у цій відповіді, спрацював.
Мелвін Рост

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

PS. Працює лише для 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();
}

Але якщо вам потрібно повернути різні параметри Map, ви можете використовувати наступні два приклади
2. Для повернення одного параметра, наприклад map:

@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 за допомогою рядка, тоді має працювати наступне:

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);
    }
    ...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.