Rückgabe des JSON-Objekts als Antwort in Spring Boot

85

Ich habe ein Beispiel RestController in Spring Boot:

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

Ich verwende die JSON-Bibliothek org.json

Wenn ich auf API drücke /hello, wird eine Ausnahme angezeigt:

Servlet.service () für Servlet [dispatcherServlet] im Kontext mit Pfad [] hat eine Ausnahme ausgelöst [Anforderungsverarbeitung fehlgeschlagen; verschachtelte Ausnahme ist java.lang.IllegalArgumentException: Kein Konverter für Rückgabewert vom Typ: Klasse org.json.JSONObject] mit Grundursache gefunden

java.lang.IllegalArgumentException: Für den Rückgabewert vom Typ: class org.json.JSONObject wurde kein Konverter gefunden

Was ist das Problem? Kann jemand erklären, was genau passiert?

iwekesi
quelle
Jackson kann JSONObject nicht in json konvertieren.
Pau
OK das verstehe ich. Was kann getan werden, um dies zu beheben?
Iwekesi
1
Ich möchte, dass die Antwort im laufenden Betrieb erstellt wird. Ich möchte nicht für jede Antwort bestimmte Klassen erstellen.
Iwekesi
2
Es ist möglicherweise besser, wenn Ihre Methode nur als String zurückgegeben wird. Zusätzlich können Sie die Annotation @ResponseBody an die Methode anhängen. Dadurch wird Ihre Antwort wie gewünscht behandelt :-)@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}
vegaasen
@vegaasen können Sie ein wenig über ResponseBody
iwekesi

Antworten:

103

Da Sie das Spring Boot-Web verwenden, ist die Jackson-Abhängigkeit implizit und muss nicht explizit definiert werden. Sie können in Ihrer pom.xmlRegisterkarte "Abhängigkeitshierarchie" nach Jackson-Abhängigkeit suchen, wenn Sie Eclipse verwenden.

Und wie Sie mit kommentiert haben, @RestControllermüssen Sie keine explizite JSON-Konvertierung durchführen. Geben Sie einfach ein POJO zurück und der Jackson Serializer kümmert sich um die Konvertierung in JSON. @ResponseBodyDies entspricht der Verwendung mit @Controller. Anstatt Platzierung @ResponseBodyauf jedem Controller - Methode setzen wir @RestControlleranstelle von Vanille @Controllerund @ResponseBodyist standardmäßig auf alle Ressourcen in diesem Controller angewendet.
Verweisen Sie auf diesen Link: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

Das Problem besteht darin, dass das zurückgegebene Objekt (JSONObject) für bestimmte Eigenschaften keinen Getter hat. Sie möchten dieses JSONObject nicht serialisieren, sondern ein POJO serialisieren. Also einfach das POJO zurückgeben.
Verweisen Sie auf diesen Link: https://stackoverflow.com/a/35822500/5039001

Wenn Sie eine json-serialisierte Zeichenfolge zurückgeben möchten, geben Sie einfach die Zeichenfolge zurück. Spring verwendet in diesem Fall StringHttpMessageConverter anstelle des JSON-Konverters.

Prem Kumar
quelle
Wenn Sie eine JSON-Zeichenfolge von Java zurückgeben möchten, können Sie eine Zeichenfolge einfach zurückgeben, wenn sie bereits json-serialisiert ist. Es ist nicht erforderlich, einen String in json und json zurück in einen String zu konvertieren.
Prem Kumar
5
Wenn Sie eine Reihe von Name-Wert-Paaren ohne eine starre Struktur zur Kompilierungszeit zurückgeben möchten, können Sie ein Map<String,Object>oder ein PropertiesObjekt zurückgeben
Vihung
@prem kumar zufällige Frage: Was meinst du mit 'anstelle von Vanilla Controller und ResponseBody'? Was ist Vanille hier?
Orkun Ozen
Ich meinte einen normalen Controller und mit ResponseBody-Annotation auf jeder Anforderungsmethode.
Prem Kumar
66

Der Grund, warum Ihr aktueller Ansatz nicht funktioniert, liegt darin, dass Jackson standardmäßig zum Serialisieren und Deserialisieren von Objekten verwendet wird. Es weiß jedoch nicht, wie man das serialisiert JSONObject. Wenn Sie eine dynamische JSON-Struktur erstellen möchten, können Sie beispielsweise Folgendes verwenden 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;
}

Dies führt zu der folgenden JSON-Antwort:

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

Dies ist etwas eingeschränkt, da das Hinzufügen untergeordneter Objekte möglicherweise etwas schwieriger wird. Jackson hat jedoch einen eigenen Mechanismus, indem er ObjectNodeund verwendet ArrayNode. Um es zu verwenden, müssen Sie ObjectMapperin Ihrem Dienst / Controller automatisch verdrahten . Dann können Sie verwenden:

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

Mit diesem Ansatz können Sie untergeordnete Objekte und Arrays hinzufügen und alle verschiedenen Typen verwenden.

g00glen00b
quelle
2
Was ist Mapper hier?
Iwekesi
1
@iwekesi das ist der Jackson, den ObjectMapperdu automatisch verdrahten solltest (siehe den Absatz über meinem letzten Codefragment).
g00glen00b
1
Es ist erstaunlich zu wissen, dass man solche Anstrengungen unternehmen muss, um aussagekräftige JSON-Objekte zu erstellen! Es ist auch traurig, dass Pivotal überhaupt keine Anstrengungen unternimmt ( spring.io/guides/gs/actuator-service ), um diese Einschränkungen hervorzuheben . Zum Glück haben wir SO! ;)
cogitoergosum
Der Import java.util.HashMap kann nicht aufgelöst werden
Hikaru Shindo
@HikaruShindo java.util.HashMapist eine Kernfunktionalität von Java seit Java 1.2.
g00glen00b
43

Sie können entweder eine Antwort wie Stringvon @vagaasen vorgeschlagen zurückgeben oder ResponseEntitydas von Spring bereitgestellte Objekt wie unten verwenden. Auf diese Weise können Sie auch zurückkehren, Http status codewas beim Webservice-Aufruf hilfreicher ist.

@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);
    }
}
Sangam Belose
quelle
1
Wenn ich JSONObject in Entitäten hinzufüge, gibt es mir wieder eine ähnliche Ausnahme
iwekesi
@ Sangam Ihre Antwort half mir für ein anderes Problem im Zusammenhang mit jackson-dataformat-xml
göttlichen
Das war eine große Hilfe! Vielen Dank!
Jones-Chris
1
Ich frage mich, warum diese Antwort nicht mehr positiv bewertet wird. Ich bin auch neu in Spring, daher weiß ich nicht, ob dies eine gute Softwareentwicklungspraxis ist. Trotzdem hat mir diese Antwort wirklich geholfen. Ich hatte jedoch große Probleme mit a JSONObject, aber da Spring Jackson verwendet, habe ich es HashMapstattdessen in a geändert, und dann hat der Code, den ich in dieser Antwort gelesen habe, funktioniert.
Melvin Roest
2
+1 für den Vorschlag von MediaType.APPLICATION_JSON_VALUE, da sichergestellt wird, dass das erzeugte Ergebnis als json und nicht als xml analysiert wird, wie es passieren kann, wenn Sie nicht definieren
Sandeep Mandori
11

Sie können hierfür auch eine Hashmap verwenden

@GetMapping
public HashMap<String, Object> get() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("results", somePOJO);
    return map;
}
maximus
quelle
6
@RequestMapping("/api/status")
public Map doSomething()
{
    return Collections.singletonMap("status", myService.doSomething());
}

PS. Funktioniert nur für 1 Wert

Dmitry
quelle
3

verwenden ResponseEntity<ResponseBean>

Hier können Sie ResponseBean oder eine beliebige Java-Bean verwenden, um Ihre API-Antwort zurückzugeben. Dies ist die beste Vorgehensweise. Ich habe Enum als Antwort verwendet. Es gibt den Statuscode und die Statusmeldung der API zurück.

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

für die Antwort ServiceStatus oder (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;
    }
}

Die Spring REST-API sollte als Antwort den folgenden Schlüssel haben

  1. Statuscode
  2. Botschaft

Sie erhalten unten die endgültige Antwort

{

   "StatusCode" : "0",

   "Message":"Login success"

}

Sie können ResponseBody (Java POJO, ENUM usw.) gemäß Ihren Anforderungen verwenden.

Ved Prakash
quelle
2

Richtigere DTO-Erstellung für API-Abfragen, z. B. entityDTO:

  1. Standardantwort OK mit Liste der Entitäten:
@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
    return entityService.getAllEntities();
}

Wenn Sie jedoch unterschiedliche Map-Parameter zurückgeben müssen, können Sie die nächsten beiden Beispiele verwenden.
2. Für die Rückgabe eines Parameters wie map:

@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
    return ResponseEntity.status(HttpStatus.CREATED).body(
            Collections.singletonMap("key", "value"));
}
  1. Und wenn Sie eine Rückgabekarte einiger Parameter benötigen (seit 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"));
}
Alexander Yushko
quelle
1

Wenn Sie ein JSON-Objekt mit einem String zurückgeben müssen, sollte Folgendes funktionieren:

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);
    }
    ...
}
Ashwin
quelle