Zwracanie obiektu JSON jako odpowiedzi w Spring Boot


85

Mam przykładowy RestController w Spring Boot:

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

Używam biblioteki JSON org.json

Kiedy /hellotrafiam na API , pojawia się wyjątek mówiący:

Servlet.service () dla serwletu [dispatcherServlet] w kontekście ze ścieżką [] zgłosił wyjątek [przetwarzanie żądania nie powiodło się; zagnieżdżony wyjątek to java.lang.IllegalArgumentException: nie znaleziono konwertera dla wartości zwracanej typu: class org.json.JSONObject] z główną przyczyną

java.lang.IllegalArgumentException: nie znaleziono konwertera dla wartości zwracanej typu: class org.json.JSONObject

Jaki jest problem? Czy ktoś może wyjaśnić, co dokładnie się dzieje?


Jackson nie może przekonwertować JSONObject na json.
Pau

Ok, rozumiem. Co można zrobić, aby to naprawić?
iwekesi

1
Chcę, aby odpowiedź była tworzona w locie. Nie chcę tworzyć określonych klas dla każdej odpowiedzi.
iwekesi

2
Lepiej byłoby po prostu zwrócić metodę jako String. Dodatkowo możesz również dołączyć adnotację @ResponseBody do metody, która obsłuży twoją odpowiedź zgodnie z żądaniem :-)@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}
vegaasen

@vegaasen, czy możesz rozwinąć trochę o ResponseBody
iwekesi

Odpowiedzi:


103

Ponieważ używasz sieci Spring Boot, zależność Jacksona jest niejawna i nie musimy jawnie definiować. pom.xmlJeśli używasz zaćmienia, możesz sprawdzić zależność Jacksona na swojej karcie hierarchii zależności.

Jak już dodałeś, @RestControllernie ma potrzeby wykonywania jawnej konwersji json. Po prostu zwróć POJO, a serializator jackson zajmie się konwersją do json. Jest równoważne użyciu, @ResponseBodygdy jest używane z @Controller. Zamiast umieszczać@ResponseBody na każdej metodzie kontrolera, którą umieszczamy @RestControllerzamiast wanilii @Controlleri @ResponseBodydomyślnie jest stosowana do wszystkich zasobów w tym kontrolerze.
Skorzystaj z tego linku: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

Problem polega na tym, że zwrócony obiekt (JSONObject) nie ma funkcji pobierającej dla niektórych właściwości. Twoim zamiarem nie jest serializacja tego obiektu JSONObject, ale serializacja POJO. Więc po prostu zwróć POJO.
Skorzystaj z tego linku: https://stackoverflow.com/a/35822500/5039001

Jeśli chcesz zwrócić serializowany ciąg json, po prostu zwróć ciąg. W tym przypadku Spring użyje StringHttpMessageConverter zamiast konwertera JSON.


jeśli ciąg json jest tym, co chcesz zwrócić z języka Java, możesz po prostu zwrócić ciąg, jeśli jest już zserializowany w formacie JSON. Nie ma potrzeby konwertowania ciągu znaków na json i json z powrotem na ciąg.
prem kumar

5
Jeśli chcesz powrócić zestaw par nazwa-wartość bez sztywnej konstrukcji kompilacji, można zwrócić Map<String,Object>lub Propertiesobiektu
Vihung

@prem kumar random question: co masz na myśli mówiąc „zamiast vanilla Controller and ResponseBody”? co tu jest wanilia?
Orkun Ozen

Miałem na myśli zwykłego kontrolera i adnotację ResponseBody umieszczaną na każdej metodzie żądania.
prem kumar

66

Przyczyną, dla której obecne podejście nie działa, jest to, że Jackson jest domyślnie używany do serializacji i deserializacji obiektów. Jednak nie wie, jak serializować JSONObject. Jeśli chcesz stworzyć dynamiczną strukturę JSON, możesz użyć Mapna przykład:

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

Doprowadzi to do następującej odpowiedzi JSON:

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

Jest to trochę ograniczone, ponieważ dodawanie obiektów podrzędnych może być nieco trudniejsze. Jackson ma jednak swój własny mechanizm, używając ObjectNodei ArrayNode. Aby z niego skorzystać, musisz automatycznie podłączyć ObjectMappersię do usługi / kontrolera. Następnie możesz użyć:

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

Takie podejście umożliwia dodawanie obiektów podrzędnych, tablic i używanie wszystkich różnych typów.


2
co to jest mapper?
iwekesi

1
@iwekesi to Jackson, ObjectMapperktórego powinieneś automatycznie połączyć (zobacz akapit nad moim ostatnim fragmentem kodu).
g00glen00b

1
To oszałamiające , że trzeba dołożyć wszelkich starań, aby stworzyć znaczące obiekty JSON! Smutne jest również to, że Pivotal nie podejmuje żadnego wysiłku ( spring.io/guides/gs/actuator-service ), aby wskazać te ograniczenia. Na szczęście mamy TAK! ;)
cogitoergosum

Nie można rozwiązać importu java.util.HashMap
Hikaru Shindo

@HikaruShindo java.util.HashMapjest podstawową funkcjonalnością Javy od wersji 1.2.
g00glen00b

43

Możesz zwrócić odpowiedź zgodnie z Stringsugestią @vagaasen lub użyć ResponseEntityobiektu dostarczonego przez Spring, jak poniżej. W ten sposób możesz również wrócić, Http status codeco jest bardziej pomocne w wywołaniu usługi internetowej.

@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
Jeśli dodam JSONObject w encjach, to znowu daje mi podobny wyjątek
iwekesi

@Sangam Twoja odpowiedź pomogła mi rozwiązać inny problem związany z jackson-dataformat-xml
boskie.

To była duża pomoc! Dziękuję Ci!
jones-chris,

1
Zastanawiam się, dlaczego ta odpowiedź nie jest bardziej przychylna. Jestem nowy w Spring, więc nie wiem, czy to dobra praktyka inżynierii oprogramowania. To powiedziawszy, ta odpowiedź naprawdę mi pomogła. Jednak miałem dużo problemów z a JSONObject, ale ponieważ Spring używa Jacksona, zmieniłem go na HashMapa i wtedy kod, który przeczytałem w tej odpowiedzi, zadziałał.
Melvin Roest

2
+1 za zasugerowanie MediaType.APPLICATION_JSON_VALUE, ponieważ zapewnia, że ​​wygenerowany wynik zostanie przeanalizowany jako json, a nie xml, co może się zdarzyć, jeśli nie zdefiniujesz
Sandeep Mandori

11

możesz również użyć do tego hashmap

@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. Działa tylko dla 1 wartości


3

posługiwać się ResponseEntity<ResponseBean>

Tutaj możesz użyć ResponseBean lub Any java bean, jak chcesz, aby zwrócić swoją odpowiedź API i jest to najlepsza praktyka. Użyłem Enum do odpowiedzi. zwróci kod stanu i komunikat o stanie interfejsu 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);
    }

dla odpowiedzi ServiceStatus lub (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 powinien mieć poniższy klucz w odpowiedzi

  1. Kod statusu
  2. Wiadomość

ostateczną odpowiedź otrzymasz poniżej

{

   "StatusCode" : "0",

   "Message":"Login success"

}

możesz użyć ResponseBody (java POJO, ENUM itp.) zgodnie z wymaganiami.


2

Bardziej poprawne tworzenie DTO dla zapytań API, na przykład entityDTO:

  1. Domyślna odpowiedź OK z listą podmiotów:
@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
    return entityService.getAllEntities();
}

Ale jeśli potrzebujesz zwrócić różne parametry mapy, możesz użyć następnych dwóch przykładów
2. Aby zwrócić jeden parametr, taki jak map:

@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
    return ResponseEntity.status(HttpStatus.CREATED).body(
            Collections.singletonMap("key", "value"));
}
  1. A jeśli potrzebujesz mapy zwrotnej niektórych parametrów (od wersji 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

Jeśli chcesz zwrócić obiekt JSON za pomocą ciągu, to powinno działać:

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);
    }
    ...
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.