Возврат объекта 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 () для сервлета [dispatcherServlet] в контексте с path [] выдал исключение [Ошибка обработки запроса; вложенное исключение - 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, не могли бы вы рассказать немного об ResponseBody
iwekesi

Ответы:

109

Поскольку вы используете Spring Boot Web, зависимость Джексона неявна, и нам не нужно определять ее явно. Вы можете проверить зависимость Джексона 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) не имеет получателя для определенных свойств. И вы намерены не сериализовать этот JSONObject, а вместо этого сериализовать POJO. Так что просто верните POJO.
Обратитесь к этой ссылке: https://stackoverflow.com/a/35822500/5039001

Если вы хотите вернуть сериализованную строку json, просто верните строку. В этом случае Spring будет использовать StringHttpMessageConverter вместо конвертера JSON.

Прем Кумар
источник
если строка json - это то, что вы хотите вернуть из java, вы можете просто вернуть строку, если она уже сериализована json. Нет необходимости преобразовывать строку в json и обратно в строку json.
Прем Кумар
6
Если вы хотите вернуть набор пар имя-значение без жесткой структуры времени компиляции, вы можете вернуть Map<String,Object>или Propertiesобъект
Вихунг
@prem kumar случайный вопрос: что вы имеете в виду под «вместо ванильного контроллера и ResponseBody»? что здесь ваниль?
Orkun Ozen 01
Я имел в виду обычный контроллер и аннотацию ResponseBody, размещенную на каждом методе запроса.
Прем Кумар
67

Причина, по которой ваш текущий подход не работает, заключается в том, что Джексон по умолчанию используется для сериализации и десериализации объектов. Однако он не знает, как сериализовать 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;
}

Этот подход позволяет добавлять дочерние объекты, массивы и использовать все различные типы.

g00glen00b
источник
2
что здесь маппер?
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, либо использовать 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 в сущности, он снова
даст
@Sangam, ваш ответ помог мне в решении другой проблемы, связанной с jackson-dataformat-xml
божественным
Это было большим подспорьем! Спасибо!
jones-chris
1
Интересно, почему этот ответ не получил больше голосов. Я тоже новичок в Spring, поэтому не знаю, насколько это хорошая практика разработки программного обеспечения. С учетом сказанного, этот ответ мне действительно помог. Однако у меня было много проблем с a JSONObject, но поскольку Spring использует Jackson, я заменил его на a, HashMapа затем код, который я прочитал в этом ответе, сработал.
Мелвин Руст,
2
+1 за предложение MediaType.APPLICATION_JSON_VALUE, поскольку он гарантирует, что полученный результат будет проанализирован как json, а не xml, как это может произойти, если вы не определите
Сандип Мандори
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();
}

Но если вам нужно вернуть разные параметры карты, вы можете использовать следующие два примера
2. Для возврата одного параметра, такого как карта:

@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);
    }
    ...
}
Эшвин
источник