Restituzione di un oggetto JSON come risposta in Spring Boot


88

Ho un esempio di RestController in Spring Boot:

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

Sto usando la libreria JSON org.json

Quando premo API /hello, ottengo un'eccezione che dice:

Servlet.service () per servlet [dispatcherServlet] nel contesto con percorso [] ha generato l'eccezione [Elaborazione richiesta non riuscita; l'eccezione nidificata è java.lang.IllegalArgumentException: nessun convertitore trovato per il valore restituito di tipo: class org.json.JSONObject] con causa principale

java.lang.IllegalArgumentException: nessun convertitore trovato per il valore restituito di tipo: class org.json.JSONObject

Qual'è il problema? Qualcuno può spiegare cosa sta succedendo esattamente?


Jackson non può convertire JSONObject in json.
Pau

Ok, lo capisco. Cosa si può fare per risolvere questo problema?
iwekesi

1
Voglio che la risposta sia costruita al volo. Non voglio creare classi specifiche per ogni risposta.
iwekesi

2
Potrebbe essere meglio che il tuo metodo ritorni come String. Inoltre, puoi anche aggiungere l'annotazione @ResponseBody al metodo, questo gestirà la tua risposta come richiesto :-)@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}
vegaasen

@vegaasen puoi approfondire un po 'su ResponseBody
iwekesi

Risposte:


109

Poiché si utilizza Spring Boot Web, la dipendenza da Jackson è implicita e non è necessario definirla esplicitamente. Puoi controllare la dipendenza da Jackson nella tua pom.xmlscheda gerarchia delle dipendenze se usi eclipse.

E come hai annotato con @RestControllernon è necessario eseguire una conversione json esplicita. Basta restituire un POJO e il serializzatore jackson si occuperà della conversione in json. È equivalente all'uso @ResponseBodyquando viene utilizzato con @Controller. Piuttosto che posizionare @ResponseBodysu ogni metodo di controller, inseriamo @RestControllerinvece di vanilla @Controllere @ResponseBodyper impostazione predefinita viene applicato su tutte le risorse in quel controller.
Fare riferimento a questo collegamento: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

Il problema che stai affrontando è perché l'oggetto restituito (JSONObject) non ha getter per determinate proprietà. E la tua intenzione non è quella di serializzare questo JSONObject ma invece di serializzare un POJO. Quindi restituisci il POJO.
Fare riferimento a questo collegamento: https://stackoverflow.com/a/35822500/5039001

Se vuoi restituire una stringa serializzata json, restituisci semplicemente la stringa. La primavera utilizzerà StringHttpMessageConverter invece del convertitore JSON in questo caso.


se la stringa json è ciò che vuoi restituire da java, puoi semplicemente restituire una stringa se è già serializzata json. Non è necessario convertire la stringa in json e json di nuovo in stringa.
prem kumar

6
Se vuoi restituire un insieme di coppie nome-valore senza una rigida struttura in fase di compilazione, puoi restituire Map<String,Object>un Propertiesoggetto o un oggetto
Vihung

@prem kumar domanda casuale: cosa intendi con "invece di vanilla Controller e ResponseBody"? che cos'è la vaniglia qui?
Orkun Ozen

intendevo un normale controller e con l'annotazione ResponseBody posta su ogni metodo di richiesta.
prem kumar

68

Il motivo per cui il tuo approccio attuale non funziona è perché Jackson viene utilizzato per impostazione predefinita per serializzare e deserializzare gli oggetti. Tuttavia, non sa come serializzare il file JSONObject. Se desideri creare una struttura JSON dinamica, puoi utilizzare Map, ad esempio:

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

Ciò porterà alla seguente risposta JSON:

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

Questo è un po 'limitato, poiché potrebbe diventare un po' più difficile aggiungere oggetti figlio. Jackson ha il suo meccanismo però, usando ObjectNodee ArrayNode. Per usarlo, devi autowire ObjectMappernel tuo servizio / controller. Quindi puoi usare:

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

Questo approccio consente di aggiungere oggetti figlio, array e utilizzare tutti i vari tipi.


2
cos'è il mappatore qui?
iwekesi

1
@iwekesi questo è il Jackson ObjectMapperche dovresti autowire (vedi il paragrafo sopra il mio ultimo snippet di codice).
g00glen00b

1
È sbalorditivo sapere che bisogna fare tanta strada per produrre oggetti JSON significativi! È anche triste che Pivotal non faccia alcuno sforzo ( spring.io/guides/gs/actuator-service ) per richiamare queste limitazioni. Fortunatamente, abbiamo COSÌ! ;)
cogitoergosum

@HikaruShindo java.util.HashMapè una funzionalità fondamentale di Java a partire da Java 1.2.
g00glen00b

44

Puoi restituire una risposta come Stringsuggerito da @vagaasen oppure puoi utilizzare l' ResponseEntityoggetto fornito da Spring come di seguito. In questo modo puoi anche restituire Http status codeche è più utile nella chiamata al servizio web.

@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
Se aggiungo JSONObject nelle entità, mi viene di nuovo un'eccezione simile
iwekesi,

@Sangam la tua risposta mi ha aiutato per un altro problema relativo a jackson-dataformat-xml
divine

Questo è stato di grande aiuto! Grazie!
jones-chris

1
Mi chiedo perché questa risposta non sia votata di più. Anch'io sono nuovo di Spring, quindi non so se questa sia una buona pratica di ingegneria del software. Detto questo, questa risposta mi ha davvero aiutato. Tuttavia, ho avuto molti problemi con a JSONObject, ma poiché Spring usa Jackson, l'ho cambiato in a HashMape poi il codice che ho letto in questa risposta ha funzionato.
Melvin Roest

2
+1 per aver suggerito MediaType.APPLICATION_JSON_VALUE in quanto assicura che il risultato prodotto venga analizzato come json e non come xml come potrebbe accadere se non si definisce
Sandeep Mandori

11

puoi anche usare una hashmap per questo

@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. Funziona solo per 1 valore


3

uso ResponseEntity<ResponseBean>

Qui puoi usare ResponseBean o Any java bean come preferisci per restituire la tua risposta api ed è la migliore pratica. Ho usato Enum per la risposta. restituirà il codice di stato e il messaggio di stato dell'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);
    }

per la risposta ServiceStatus o (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;
    }
}

L'API REST di primavera dovrebbe avere la chiave sotto in risposta

  1. Codice di stato
  2. Messaggio

riceverai la risposta finale di seguito

{

   "StatusCode" : "0",

   "Message":"Login success"

}

puoi usare ResponseBody (java POJO, ENUM, ecc.) secondo le tue esigenze.


2

Più corretto creare DTO per le query API, ad esempio entityDTO:

  1. Risposta predefinita OK con elenco di entità:
@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
    return entityService.getAllEntities();
}

Ma se hai bisogno di restituire diversi parametri di Map, puoi usare i prossimi due esempi
2. Per restituire un parametro come map:

@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
    return ResponseEntity.status(HttpStatus.CREATED).body(
            Collections.singletonMap("key", "value"));
}
  1. E se hai bisogno della mappa di restituzione di alcuni parametri (da 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

Se devi restituire un oggetto JSON utilizzando una stringa, dovrebbe funzionare quanto segue:

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);
    }
    ...
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.